BASIC és assembler
Bóc István
Műszaki Könyvkiadó, 1985
Tartalom
Előszó
Az elmúlt években hazánkban is rohamosan nőtt a személyi számítógéppel rendelkezők száma. Házi használatra elsősorban a ZX81 és ZX SPECTRUM típusok terjedtek el. Ez a könyv a ZX81 programozásáról és - példákon keresztül - alkalmazási lehetőségeiről szól.
A személyi számítógépek iránt érdeklődök többnyire elsajátítják a BASIC nyelv alapjait, és ez a felvetődő problémák megoldásához általában elegendő. Így a gépi kódban való programozás valamilyen igen nehéz, és speciális feladatokra alkalmazható módszernek tűnik. Ezt a tévhitet támasztja alá még az is, hogy a gépi kódú programozásról megjelenő irodalom "profi" felhasználóknak szól, így gyakran használ kezdők számára nem érthető fogalmakat, szakkifejezéseket. Sokaknak okoz problémát, hogy nem találnak gyakorlati útmutatást a gépi kódú programok beviteléré és a futtatás módjára.
A könyv célja kettős: egyrészt bemutatni a BASIC nyelven való programozásnak néhány olyan módszerét, amely szinte kizárólag személyi számítógépen alkalmazható - újabb lehetőségeket adva a hagyományos eljárásokhoz -, másrészt a BASIC programozás határain túllépve bevezetni az olvasót a gépi kódban való (ASSEMBLER) programozásba. Az ismertetett módszerek természetesen nemcsak ZX81-re alkalmazhatók, a példaprogramokon azonban kisebb változtatások szükségesek, ha más típusú gépen akarjuk futtatni őket.
A könyv megírása során igyekeztem kerülni azokat a szakkifejezéseket, amelyek nem közismertek. Feltételeztem ugyan a BASIC nyelv alapjainak ismeretét, de a könyv megértéséhez még ez sem alapkövetelmény. Persze legjobb segítség a leírtak megértéséhez, ha sikerül egy ZX81 mellett olvasni, és mindent azonnal kipróbálni.
A könyv egyaránt szól kezdőknek és haladóknak, némiképp a gépkönyvet is igyekszik pótolni. Az első két fejezet a ZX81 felépítését, utasításkészletét mutatja be. A harmadik fejezet a személyi számítógépek BASIC programozási technikáit ismerteti, megmutatva a BASIC programozás határait. A gépi kódban való programozásba a IV-V. fejezet nyújt betekintést. A IV. fejezet a Z-80 (ill. a Z-80A) mikroprocesszort mutatja be, az V. pedig az ASSEMBLY programozásra ad példákat. A függelék a gép használatát megkönnyítő táblázatokat tartalmazza.
Ezúton mondok köszönetet Mátyus Györgynek, akinek segítsége nélkül a IV-V. fejezet nem készült volna el.
I. Ismerkedés a ZX81 személyi számítógéppel
I/1. Az első igazán olcsó számítógép
1980-ban jelent meg a piacon az angol SINCLAIR cég személyi számítógépe a ZX80, majd egy évvel később továbbfejlesztett változata, a ZX81. A korábbi, hasonló célú típusokhoz képest a legnagyobb újítás a gép leegyszerűsített kivitele volt. Ez természetesen a gép árában is tükröződött, máig is ez a legolcsóbb
típus. A tervezők ügyeltek arra, hogy minél több elterjedt híradástechnikai eszközt lehessen csatlakoztatni perifériaként. A display televízióra, és a háttértár-magnetofonra szinte semmilyen megkötés nincs, a legolcsóbb típusok is megfelelnek. Ugyancsak ügyes kereskedelmi fogásnak bizonyult, hogy az alapgép igen kicsi szabad memória-(tár)-területtel rendelkezik (ennyivel is olcsóbb), a memóriabővítések pedig külön vásárolhatók.
Forradalmi újításnak számított a ZX81-en az "egy billentyű- egy utasítás" elv. Ez azt jelenti, hogy minden BASIC utasításnak egy billentyű felel meg, így pl. a GOSUB utasítást nem öt betű bepötyögésével, hanem egy leütéssel lehet megadni. Ez némi gyakorlás után meggyorsítja a programok begépelését (bár kezdetben keresgélni kell).
A ZX81 bekapcsolás után azonnal értelmezi a BASIC nyelvet. Egyéb magas szintű programozási nyelv (pl. FORTH) csak megfelelő segédprogram beolvasása után alkalmazható. Ezenkívül természetesen mód van gépi kódú programok futtatására is.
Az előnyös tulajdonságok mellett meg kell említeni a gép gyenge pontjait is. A kis méret kényelmetlenné teszi a billentyűzet használatát. Az egyszerű klaviatúra másik hátránya, hogy lehetetlen a képernyő állandó szemmel kisérése nélkül írni, mert gyakran kimarad, esetleg megduplázódik egy-egy beütött karakter. Gondot okozhat a memóriabővítés (és az egyéb, itt beköthető perifériák) csatlakozása is. Ha a memóriabővítést gyakran húzzuk ki-be, a csatlakozó elkopik, az érintkezés bizonytalanná válik. Sokan úgy segítenek ezen, hogy a memóriabővítést szigetelőszalaggal a géphez rögzítik, ami nem túl esztétikus, de praktikus megoldás.
Mindezekért a hátrányokért bőséges kárpótlás, hogy a gép ára - szolgáltatásaihoz képest - igen alacsony, legalábbis külföldön. Biztató jel, hogy a ZX81 típus világszerte igen elterjedt, könnyen beszerezhető, így - hasonlóan a kalkulátorok rohamos árcsökkenéséhez várhatóan hazai ára is leesik majd.
I/2. Utasítások beadása, favitása, szerkesztése
Minden BASIC nyelvű program számozott utasítások sorozatából áll. A számítógép a program futásakor sorra végrehajtja az utasításokat, hacsak a végrehajtott utasítás nem ír elő más sorrendet. Ez mindaddig tart, amíg STOP utasítás nem következik, vagy a gép hibás utasítással nem találkozik, ill. amíg az utolsó sorig nem ér.
A programbeírás módja a ZX81 esetében sok ponton eltér a hagyományostól. Nagy előny, hogy a gép nem fogad el formailag hibás utasításokat, így ezekre a hibákra már a beütéskor (és nemcsak a futtatáskor) fény derül.
A személyi számítógépek klaviatúrájának billentyűi több jelentésűek. A ZX81 esetében egy billentyű többnyire öt funkciót lát el. Az, hogy melyik jelentés érvényes éppen a billentyű lenyomásakor attól függ, hogy a gép milyen módban van. Az érvényes módot a képernyőn látható jelzés (cursor) mutatja. A ZX81 esetében a következő módok lehetségesek:
K - keyword, azaz utasításmód,
L - letter, azaz betűmód,
F - function, azaz függvénymód,
G - graphics, azaz grafikus mód.
Válasszuk példaként az "R" gombot, és figyeljük meg az ábrán különböző jelentéseit!
Hogyan szabályozhatjuk, hogy milyen módban legyen a ZX81? Bekapcsoláskor, ill. ha a gép új utasítást kap, "K" módban van, és amíg a sorszámot ütjük be, így is marad. Ezután annak a billentyűnek az utasítása érvényesül, amelyiket lenyomjuk, majd a gép automatikusan "L" módba kerül. Most minden billentyű az írógépről ismert elrendezés szerint egy betűt jelent. A ZX81 csak nagy betűt használ, kis betűk írására nincs mód. Ha a billentyűt a "SHIFT" gombbal együtt nyomjuk meg, a jobb felső sarokba irt értelmezést kapjuk. A "GRAPHICS" (SHIFT és 9) lenyomására a gép grafikus módba lép. Ekkor a billentyűk lenyomásakor a betűk inverze jelenik meg. Ha "G" módban egy billentyűt a "SHIFT" gombbal együtt nyomunk meg, akkor jobb alsó sarokban levő grafikus jelet kapjuk. Ha ilyen jel nincsen, akkor a jobb felső sarokba irt jel inverze jelenik meg (pl. a P betű esetén inverz idézőjel). A gép mindaddig "G" módban marad, míg a "GRAPHICS" vagy a "NEW LINE" gombok egyikét le nem nyomjuk; ekkor ismét "L" módba tér át. Az "F" módot a "FUNCTION" (SHIFT és NEWLINE) lenyomásával érhetjük el, innen a visszatérés automatikus. Ugyancsak automatikus az "L" módból "K" módba való áttérés a "THEN" (SHIFT és 3) lenyomása után, mivel ekkor szükségképpen utasítás következik.
A beirt utasítást "NEW LINE"-nal zárjuk. Ha az utasítás sorszámmal kezdődik, a gép a sorszámnak megfelelően beilleszti a korábban beütött utasítások sorába. Ha az utasítás elé sorszámot nem írunk, akkor azonnal végrehajtásra kerül. Ha egy programot, vagyis egy utasítássorozatot akarunk beírni, az utasítások tetszőleges sorrendjét választhatjuk a beírásra, mivel amúgy is sorszám szerint kerülnek majd tárolásra a gépben. Aki már programozott, tudja, hogy ritka az olyan eset, amikor az elkészült program azonnal jól működik, hibát nem tartalmaz. Az elkövethető hibák két fő csoportra oszlanak: az egyik a formai (szintaktikai) hiba, a másik a program felépítési hibája (ide sorolható pl. a változók összecserélése is). Mivel gyakran szükséges egy-egy utasítás "beszúrása" két régi közé, az utasításokat már eleve tízesével szokás számozni.
A szintaktikai hiba azt jelenti, hogy a beirt utasítás formája nem felel meg a gép előírásainak (pl. ha a PRINT utasításban kettősponttal választunk el két változót). Szerencsér-e a ZX81 szintaktikailag hibás utasítást nem hajlandó felvenni a programlistára. Ha ilyen hibát tartalmazó utasítást próbálunk beadni, a "NEW LINE" lenyomását követően a gép "nem veszi be" az utasítást, hanem a hiba mellett egy "S" jelzés jelenik meg. Ilyenkor nem kell az egész utasítást újra beütni, csak a hibás részt javítani a következőképpen:
A <- és -> nyilakkal (SHIFT és 5, ill. SHIFT és 8) a cursort a hibás karakter mögé visszük, és a RUBOUT (SHIFT és 0) gombbal törölünk. Ugyanakkor tetszőleges számú új karaktert is beiktathatunk, mert a sor további része hátrább csúszik. A kijavított utasítást már bevihetjük a gépbe a NEWLINE gombbal.
Az egyéb hibák általában a program futásakor derülnek ki, ilyenkor vagy rossz eredményt kapunk, vagy a program futása megszakad, és a képernyőn az un. hibakód jelenik meg a hibás utasítás számának kíséretében. A hibakódok jegyzéket a könyv 2. függeléke tartalmazza.
A formailag hibátlan utasítás kajavitásához sem szükséges a teljes utasítást újra beütni. Ha csak egy részt kell kijavítani, akkor a következő a teendő: A fel- és lefelé mutató nyilakkal SSHIFT és 6, ill. SHIFT és 7) a programlistából kiválaszthatjuk a javítandó utasítást. A programlistában a legutóbb beirt utasításra egy kis nyíl mutat, ezt mozgatva tudjuk a kívánt utasítást kijelölni. Ha a jelzés messze van a kívánt utasítástól, akkor a gyors mozgatására két módszer is kínálkozik:
Ha a jelzés a kívánt utasításra mutat, az EDIT (SHIFT és 1) billentyű lenyomásával az utasítást "előveszük", és az ismertetett módon kijavíthatjuk.
Fontos megjegyezni, hogy az EDIT alkalmazásakor az eredeti utasítás egészen addig nem törlődik a program listáról, míg ugyanazzal a számmal újabb utasítást nem adunk be. Ez a program beírásakor egyszerűsítésre ad lehetőséget, amennyiben több hasonló utasítást akarunk beírni. Tegyük fel pl. hogy következő utasításokat akarjuk beadni:
10 IF A=1 THEN GOTO 100
20 IF A=5 THEN GOTO 120
A legegyszerűbb eljárás a következő:
Így mind a két utasítás a kívánt formában felkerül a listára.
Ha valamelyik utasításról kiderül, hogy felesleges, az utasítás számát beírva, majd a NEW LINE gombot lenyomva törölhetjük.
I/3. A programok tárolása
A ZX81-en elkészített programjainkat szinte bármilyen kazettás magnetofonon tárolhatjuk. A célnak a kevésbé jó minőségű kazetták is megfelelnek, de ha a programot gyakran használjuk, fennáll annak a veszélye, hogy a szalagon a jelszint lecsökken, és a beolvasás bizonytalanná válik. Ilyenkor mindenképpen jobb minőségű kazettát használjunk, ha a magnetofon alkalmas rá, akkor legjobb a króm-dioxid szalagok használata. Ne használjunk olyan magnót, ahol a felvétel szintszabályozása automatikus. Ekkor ugyanis a felvétel kezdetén a jelszint igen erős (a magnó automatikája az alapzaj erősségére áll be), és ez a visszaolvasásnál gyakran zavart okoz. A program tárolásához először a magnetofon mikrofonbemenetét kössük össze a ZX81 "MIC" kimenetével. A felvétel készítése idején húzzuk ki az "EAR" bemenetből a csatlakozót. A ZX81 igen érzékeny a felvétel jelszintjére, ezért a felvételerősséget szabályozó gombot közel a maximumra kell állítani. A jó felvétel szintje egy zenei felvétel erősebb hangjaival azonos erősségű.
A felvétel a SAVE "EGY NEV" utasítással történik (az idézőjelek közé a program neve kerül). Először a szalag elindítása nélkül célszerű beadni az utasítást, és beállítani a magnetofonon a felvétel erősségét. Ezután a beállított szinten biztonsággal elkészíthetjük a felvételt. Vigyázat! A kazettás magnetofont a felvétel idejére a lehető legtávolabb helyezzük el a televíziótól, hogy elkerüljük az esetleges zavaró jelek rögzítését. Minden esetben a SAVE beütése után, de a NEW LINE lenyomása előtt indítsuk el a szalagot! A program neve max. 10 karakter lehet.
A korábban rögzített (gyári vagy saját) programok beolvasásához a ZX81 "EAR" bemenetét a magnetofon fülhallgató- (hangszóró-) kimenetével kell összekötni. Nagyon fontos, hogy a beolvasós idejére a ZX81 "MIC" bemenetéből a csatlakozó ki legyen húzva. A kis teljesítményű, hordozható magnetofonok használatakor a bejátszást közel maximális hangerővel kell végezni. Nagyobb teljesítményű magnó használatakor vigyázni kell a bemenő jel erősségére, mivel a túl erős jel tönkreteheti a ZX81-et. Természetesen használhatunk szalagos magnetofont is, csak egy kissé kényelmetlen.
A programok behívása a LOAD "A NEV" utasítással történik, az idézőjelek között a beolvasandó program nevét kell írni. Ha a LOAD után csak két idézőjelet adunk be, akkor a szalagról a legközelebbi programot tölti be a gép.
I/4. A ZX81 felépítése
Ahhoz, hogy egy számítógépet programozzunk, nem feltétlenül szükséges ismernünk áramköreinek felépítését. Mégis az alapvető egységek ismerete számos új lehetőséget nyit meg a BASIC nyelvű programok készítésekor is, a gépi kódú programok írásánál pedig elengedhetetlen.
A ZX81 a következő főbb egységeket tartalmazza:
A különböző áramköröket az un. "busz" (sín) vezetékek kötik össze. Aszerint, hogy az adott vezeték milyen jellegű információ továbbításra szolgál, adat-buszról, cím-buszról, vagy író-olvasó buszról beszélünk.
I/5. A ZX81 memóriájának felépítése
A memóriák kétállapotú áramkörök. A két állapotot 0-nak ill. 1-nek nevezzük, aszerint, hogy a memória kimenetén magas, vagy alacsony feszültség van-e. Egy ilyen elemi információegységet nevezzünk 1 bitnek. A RAM memóriáinak állapotát a legutóbb kapott jel határozza meg, a ROM állapota viszont a gyártás során beállított értékeket őrzi.
Egy memóriacellával csak kettőig tudunk számolni, kettővel viszont már négyig, nyolc memóriacellával pedig 256-ig (0-255). Mivel ez a szám többnyire elegendő a különböző információk megkülönböztetésére, a memóriák 8 cellából álló egységekből állnak (rekesz), egy ilyen egységet nevezünk 1 byte-nak. 1024 byte tárolókapacitást 1 kilobyte-nak hívunk. Az egyes memóriarekeszeknek számuk (címük) van.
A ZX81 memória felépítésében a ROM a 0000 és a 8191 címek között helyezkedik el. A 8192 és 16383 közötti sorszámok nem kerültek felhasználásra.
A felhasználó a 16384-nél magasabb sorszámú memóriákba írhat. Az alap ZX81 legnagyobb memóriaszáma a 17407, memóriakibővítés esetén ez a szám természetesen magasabb (4K bővítés esetén 21503; 8K-25599; 16K-33791; 32K-50175; 64K-82943). A 16384 és 16508 címek között a ZX81 rendszerváltozóit helyezték el. A rendszerváltozókról később még lesz szó, mindenesetre ide kerülnek pl. azok a címek is, melyek az egyes memóriaterületek határát jelentik.
A programot a gép a 16509 címtől kezdve tárolja. Közvetlenül a program után a képernyő információit tároló "DISPLAY FILE" található, ezután pedig a változók (VARIABLES) következnek. Ezt követi az éppen begépelés vagy szerkesztés alatt álló sor tárolása, és a kalkulátor rész. A következő memóriaterületet (MACHINE STACK) a rendszer vezérlésére közvetlenül a mikroprocesszor használja. Végül a GOSUB STACK következik, ahol a BASIC program futását vezérlő információk helyezkednek el. E fölé a terület fölé gépi kódú programok kerülhetnek.
Nézzük meg, hogyan tárolják az egyes területek az információt! A BASIC program minden egyes sora a következőképpen helyezkedik el:
1-2. byte: | a sor száma; |
3-4. byte: | a programszöveg byte-jainak száma +1; |
5. byte-tól: | az utasítás szövege, ahol az utolsó byte-ban 118 van, ami a sor végét jelzi (NEW LINE). |
A sorszám, ill. a hossz tárolására azért kell 2-2 byte-ot felhasználni, mert e számok 255-nél nagyobbak is lehetnek. Ezesetben a tárolt értéket úgy kapjuk meg, hogy a 2. byte értékéhez hozzáadjuk az első szám 256-szorosát. A 3-4. byte közül viszont a 4. tárolja a nagyobb helyértékű számjegyeket. A programszöveg tárolása az egyes karakterek kódjával történik. A karakterkódokról részletesebben a II. fejezetben lesz szó, a karakterkód-táblázatot pedig az 1. függelék tartalmazza.
A DISPLAY FILE-on lévő információ a következő módon tárolódik:
1. byte: | 118; |
2-33. byte | a képernyő legfelső sorában levő karakterek kódjai; |
34. byte: | 118; |
35-66. byte: | a második sor; |
67. byte: | 118; |
... |
|
761-792. byte: | az utolsó (24.) sor; |
793 byte: | 118 |
A DISPLAY FILE csak akkor tárolja így a képernyő képét, ha a memóriában erre bőségesen van hely (több mint 3.25 kilobyte még szabad). Ha a rendelkezésre álló terület szűkös, akkor csak a teleirt sorok foglalnak el 32 byte-ot, a többi sornál a sor végén lévő üres helyek kódjai nem szerepelnek (ekkor a sor végét a 118-as szám jelzi). A DISPLAY FILE kezdőcímét - mivel az a BASIC program hosszától függ - a megfelelő rendszerváltozó mutatja.
Mint már említettük, a változók nevének és értékének tárolása közvetlenül a DISPLAY FILE után történik. Ha egy betűvel jelölünk egy numerikus változót, akkor tárolása 6 byte-ot vesz igénybe:
1. byte: | a változó nevének kódja + 64; |
2. byte: | az exponens értéke, ezzel a tizedespont helyét határozzuk meg; |
3-6. byte: | Mantissza byte-ok, ahol az első bit az előjelet jelzi (0=pozitív, 1=negatív); a mantissza byte-ok egy 0.5 és 1 közötti értéket adnak. Az aktuális értéket a MANT*2^(EXP-128) kifejezéssel számíthatjuk. |
A több karakterrel jelölt változók tárolása is hasonlóképpen történik, de ekkor a változó teljes neve kerül az első byte-okba, és csak ezután következik az exponens. A változó nevének utolsó karakterét a megfelelő kód + 128 jelöli.
A tömbváltozók tárolásának módja a következő:
1. byte: | a változó nevének kódja + 96; |
2-3. byte: | a változó tárolására szükséges byte-ok száma; |
4. byte: | a dimenziók száma; |
5. byte: | az első dimenzió; |
... | |
n. byte: | az utolsó dimenzió; |
n+1 byte: | a tömbelemek értékei (elemenként 5 byte). |
A tömbváltozók értékeinek tárolási sorrendjét a B(2,2,3) tömbön mutatjuk be:
1. elem: | B(1,1,1) | 7. elem: | B(2,1,1) | |
2. elem: | B(1,1,2) | 8. elem: | B(2,1,2) | |
3. elem: | B(1,1,3) | 9. elem: | B(2,1,3) | |
4. elem: | B(1,2,1) | 10. elem: | B(2,2,1) | |
5. elem: | B(1,2,2) | 11. elem: | B(2,2,2) | |
6. elem: | B(1,2,3) | 12. elem: | B(2,2,3) |
Egy változó értéke nemcsak szám, hanem szöveg is lehet (ezzel a II. fejezetben foglalkozunk majd részletesen). A szöveges változók tárolási módja a következő:
1. byte: | a változó nevének kódja + 32; |
2-3. byte: | a tárolt karakterek száma; |
4. byte-tól: | a szöveg. |
A szöveges tömbváltozók tárolása igen hasonló a numerikus tömbváltozókéhoz:
1. byte: | a változó nevének kódja + 160; |
2-3. byte: | a tároláshoz szükséges byte-ok száma; |
4. byte: | a dimenziók száma; |
5. byte: | az első dimenzió; |
... | |
n. byte: | az utolsó dimenzió; |
n+1 byte-tól: | az elemek. |
A ZX81 memóriaszerkezetének ismerete a BASIC programokban számos egyszerűsítést és ésszerűsítést tesz lehetővé. Ahhoz, hogy élni tudjunk ezekkel a lehetőségekkel, először ismerjük meg a ZX81 utasításkészletét és függvényeit.
II/1. Változók
A számítógépes programokban két fő típusra oszthatók a felhasznált változók aszerint, hogy a változóban tárolt adat értéke szám vagy szöveg (string).
Már a változó neve is jelzi, hogy a változó milyen jellegű információt tárol. A numerikus változók neve betűk és számok tetszőleges kombinációja, de az első karakternek mindenképpen betűnek kell lennie. Változónévként tehát pl. megengedett az A, A145, KRUMPLISULYA de nem jó az 1A45 (mert nem betűvel kezdődik), a KRUMPLI SULYA (mert a betűköz nem megengedett), vagy az A?B, A.B (mert nemcsak betű és szám kombináció).
A ZX81 háromféle numerikus változót használ:
Az egész számok maximum nyolc jegyűek lehetnek, valós számokon pedig a legfeljebb nyolcjegyű előjeles tizedestörtet ért a gép.
Az exponenciális alakban adott szám értékes jegyei (a mantissza) után egy E betűt majd a kitevőt kell innunk, ami megadja, hogy 10 melyik hatványával kell a mantisszát megszorozni. Pl: 3.41E5 értéke 341000. A kitevő lehet pozitív, negatív vagy nulla, de csak egész szám (tehát gyök 10-et tehát nem írhatjuk 1E0.5 alakban).
A ZX81 használatakor a három változótípus közötti átmenet automatikus. Ha a változó abszolút értéke 10^8-nál nagyobb, vagy 10^-6-nál kisebb lesz, automatikusan exponenciális alakban tárolódik tovább. Az exponenciális alakban irt számok viszont valós formájúak lesznek, ha az említett határok közé kerülnek. A valós és egész számok közötti különbség is elmosódott. Megengedett valós számok használata olyan esetekben is, ahol csak egész értékek értelmezettek (pl. GOTO utasításban, vagy egy vektorban indexként stb.). Ilyenkor a ZX81 az egészre kerekített értéket veszi figyelembe, A(I) tehát I=1.49 esetén A(1), I=1.5 esetén A(2) lesz.
A ZX81-ben csak 2^128-nál (1.62*10^38) kisebb abszolút értékű számokat tudunk értelmezni, ugyanakkor a számítógép csak a 2^-128-nál (2.94*10^-39) nagyobb
abszolút értékű számokat tudja a nullától megkülönböztetni, az ennél kisebbeket már nullának veszi.
Ha a változóban szöveget (stringet) tárolunk, azt a változó nevében is jeleznünk kell. A szövegváltozó nevének egy betüből, és a $ karakterből kell állnia. Így tehát a W$ megengedett szövegváltozó név, a WW$ nem. A szövegváltozó értékét minden esetben idézőjelek közé kell tenni.
A ZX81 BASIC nyelve a szövegváltozók igen kényelmes kezelését biztosítja, mert lehetőséget nyújt a szövegváltozó részekre bontására, szeletelésére. Legyen pl. A$ értéke "VILLAMOSJEGY" . Az AS(I) kifejezés értéke az adott szöveg I. karaktere lesz. Így pl.
AS(1)="V"
AS(2)="I"
stb.
Az A$(I TO J) kifejezéssel az I. és J. karakterek közötti részt emelhetjük ki, a határokat is beleértve, példánkban tehát
A$(1 TO 5)="VILLA",
AS(6 TO 8)="MOS",
A$(9 TO ,12)="JEGY"
Az A$( TO 5) értéke megegyezik A$(1 TO 5) értékével az A$(4 TO ) értéke pedig megegyezik AS(4 TO n)-nel, ahol n a string hossza. Ezek szerint a szöveg végének kiemeléséhez nem kell ismerni szöveg hosszát.
A szövegváltozók elnevezési szabálya csak 26 szövegváltozó definiálásat teszi lehetővé. Ha ennél többre van szükség, akkor tömbváltozókat kell definiálnunk, ez a változók kezelése szempontjából is kényelmesebb. A tömbváltozókról részletesebben később lesz szó.
II/2. A ZX81 függvényei
A ZX81 függvényei öt csoportra bonthatók:
a) Matematikai és trigonometrikus függvények
1. táblázat - A ZX81 matematikai és trigonometrikus függvényei:
Függvény | Billentyű | Jelölés |
sin | Function Q | SIN |
cos | Function W | COS |
tan | Function E | TAN |
arc sin | Function A | ASN |
arc cos | Function S | ACS |
arc tan | Function D | ATN |
egész rész | Function R | INT |
előjel | Function F | SGN |
abszolút érték | Function G | ABS |
négyzetgyök | Function H | SQR |
e alapú logaritmus | Function Z | LN |
e^x | Function X | EXP |
b) Stringeken értelmezett függvények
A szövegeken értelmezett függvények értéke minden esetben szám (a VAL függvény kivételével egész szám). A ZX81 három ilyen függvényt értemez: a VAL, a LEN és a CODE függvényt.
A VAL (value) függvény (Function J) olyan szövegvoltozókra értelmezhető, amelyek számot, ill. algebrai kifejezést jelölnek. A függvény értéke az adott szám, ill. kifejezés értéke lesz. Pl: VAL "2"=2; VAL "2.2"=2.2 ; VAL "2E2"=200.
A VAL "A" kifejezés is értelmes, ha az A változót korábban értelmeztük.
A LEN (length) függvényt (Function K) tetszőleges szövegen (stringen) értelmezhetjük, az eredmény az adott szöveg karaktereinek száma, azaz a string hossza. Pl. LEN "BABA"=4; LEN "EZ BIZONY HOSSZU"=16.
A CODE függvény az adott szövegváltozó első karakterének kódját adja eredményül. Pl. CODE "ALMA"= CODE "A"=38.
A ZX81 karakterkészletének kódjait az 1. függelék tartalmazza.
c) Szöveget eredményező függvények
E függvények egész számokon vannak értelmezve, és eredményül szöveget adnak. Ezt a tulajdonságukat már a nevük mögött álló $ karakter is jelzi. A függvények nem egész számra is alkalmazhatók, ilyenkor a kerekített értékhez tartozó szöveget adják meg. A ZX81-en két ilyen függvény van, a CHR$ és a STR$.
A CHR$ (character) függvénynek (Function U) a 0-255 tartományon van értelme, eredményül azt a karaktert adja, amelynek kódja az adott szám. Ez a függvény gyakorlatilag a CODE függvény inverze.
A STR$ (String) függvény (Function Y) az adott szám vagy kifejezés értékét szövegként azaz a szám karaktereinek sorozataként adja meg. Például:
STR$ 2E2 ="200" ;STR$ 2E15="2E+15"; STR$ (2*3)="6"; STR$ A ="154" , ha az A változó értéke 154.
Ugyancsak szöveget ad eredményül az INKEY$ függvény is. E függvényről később lesz szó.
d) Logikai függvények
A logikai függvények értéke 0, ha az állítás hamis, és 1, ha igaz. A hagyományos logikai függvényeken kívül (AND, OR, NOT) ide tartoznak a különböző matematikai relációk is: <, >, <=, >=, <>, =. A NOT kivételével mindegyikük kétváltozós.
Az OR (vagy) függvény (Shift és W) alakja a következő: A OR B, ahol A és B lehet algebrai kifejezés, változó, matematikai reláció, vagy egyéb logikai függvény. Az A OR B kifejezés értéke A, ha B=0. Ha pedig B nem nulla, a kifejezés értéke egy. Ebből is látható, hogy az A OR B kifejezés csak logikailag egyezik meg a B OR A kifejezéssel, matematikailag más számot is kaphatunk. Pl. 2 OR 0 ugyanúgy "igaz", mint 0 OR 2, ugyanakkor numerikusan az előző értéke 2, az utóbbié 1.
Az AND (és) függvényt (Shift 2) az A AND B kifejezésben használjuk, aminek értéke 0, ha B=0. Ha B különbözik zérustól , a kifejezés értéke A lesz (tehát az A AND B csak logikailag azonos B AND A értékével). A kifejezésnek akkor is van értelme, ha A helyére szövegváltozó kerül. A$ AND B értéke A$, ha B nem nulla, ellenkező esetben üres stringet ("") kapunk. Nincs viszont értelmezve a B AND A$
kifejezés.
A NOT (nem) függvény (Function N) nullára alkalmazva 1-et ad eredményül, egyébként pedig a teljes értelmezési tartományon 0.
A < (Shi+t N), > (Shift M), <= (Shift R), = (Shift L), <> (Shift T) és >= (Shift Y) matematikai relációk lényegében kétváltozós függvények, amelyek szövegváltozókra is értelmezettek. Ha az egyenlőség, ill. egyenlőtlenség fennáll, a függvény értéke 1 lesz. Ellenkező esetben eredményül nullát kapunk. Két string összehasonlításakor az a nagyobb, amelyiknek első karakterének kódja nagyobb. Egyenlőség esetén a második, majd harmadik stb. karakterek kerülnek összehasonlításra. Ezzel kényelmesen lehet szavakat rendezni, ugyanis a betűkarakterek kódjai abc sorrendben növekednek. Így pl. "ALMA"<"BARACK", "BAB"<"BABOK".
e) Egyéb függvények
Van még a ZX81-en három olyan függvény, ami nem sorolható az iménti kategóriákba. Ezek az RND, a PEEK és a USR függvények.
Az RND függvény (Function T) véletlen számok előállítására szolgál. A függvény elnevezés itt matematikailag nem pontos, hiszen az RND-nek argumentuma, azaz értelmezési tartománya nincsen. Az RND érteke 0 és 1 közé eső, gyakorlatilag véletlen számnak tekinthető, ami 0 lehet, de 1 nem. Az RND függvény a ZX81 bekapcsolása után mindig ugyanazt a számsorozatot állítja elő, hacsak nem használjuk a RAND utasítást.
A PEEK függvénnyel (Function 0) az egyes memóriarekeszek tartalmát hívhatjuk elő: a PEEK n kifejezés érteke az n című memóriarekeszben tárolt szám. A PEEK függvénnyel mind a ROM, mind a RAM. memóriaterületek meghívhatók. A PEEK függvény használatáról a III. fejezetben bővebben lesz szó
A USR függvény a gépi kódú programok futtatására szolgál, használatát az V. fejezet ismerteti.
II/3. Algebrai kifelezések
Az algebrai kifejezések kiszámításakor az egyes műveletek elvégzésének sorrendjét az ún. prioritások határozzák meg. Ismert, hogy a számítógépek, sőt a komolyabb kalkulátorok is, pl. az 5+4*3 műveletsorozat eredményéül nem a leírási sorrendnek megfelelő 9*3=27-et adják eredményül, hanem a nagyobb prioritású szorzást végzik el előbb; így az eredmény 5+12=17 lesz.
A ZX81 rendszerében a különböző műveletek, függvények prioritása a következő:
2. táblázat - Prioritások:
Művelet | Prioritás |
zárójel | 12 |
egyváltozós függvények (NOT kivételével) |
11 |
hatványozás | 10 |
előjel | 9 |
szorzás, osztás | 8 |
összeadás, kivonás | 6 |
matematikai relációk | 5 |
NOT | 4 |
AND | 3 |
OR | 2 |
II/4. Utasítások
A ZX81 BASIC nyelvének utasításai négy csoportba oszthatók:
a) Vezérlőutasítások
A BASIC programban szereplő utasítások a sorszámuknak megfelelő sorrendben kerülnek végrehajtásra. Az ettől való eltérés a vezérlőutasításokkal érhető el, azaz az utasítások sorszáma mellett a vezérlőutasítások határozzák meg az utasítások végrehajtósának sorrendjét. Vezérlőutasítások a GOTO, GOSUB, RETURN, FOR-NEXT utasítások.
A GOTO utasítás tipikus alakjai a következők:
10 GOTO 30
10 GOTO 2*30
10 GOTO A
10 GOTO 3*A+B
10 GOTO A+(B>1)*100+(130 AND B=8)
A GOTO utasítás hatására a program futása a GOTO után álló számnak, vagy kifejezésnek megfelelő sorszómon folytatódik. Ha a kifejezés értéke nem egész szám, akkor a ZX81 a kerekít, ha pedig nincs ilyen sorszámú utasítás, akkor az adott szám után következő utasításra ugrik a program. Pl. ha van 100-as és 200-as sorszámú utasítás, de sem a 100 alatti, sem a 100 és 200 közötti sorszámokat nem használtuk, akkor a GOTO 90 és a GOTO 100.49 utasítások hatása a GOTO 100 hatásával egyezik meg, míg a GOTO 100.5, GOTO 120 vagy GOTO 200.3 már a 200-as sorszámú utasításnak adja át a vezérlést.
A GOSUB utasítás formája és szabályai megegyeznek a GOTO utasításával. A GOSUB utasítás hatására szintén ugrás következik be az utasítások végrehajtási sorrendjében, de ez esetben a gép megjegyzi a GOSUB utasítás sorszámát, és amikor a programban RETURN utasítás következik, akkor a futás a GOSUB utáni utasítással folytatódik.
Ha a RETURN utasítást már több GOSUB előzte meg, akkor a legutoljára végrehajtott GOSUB mögé tér vissza a program. A korábbi GOSUB utasítások mögé újabb RETURN utasításokkal juthatunk. Így tetszőleges számú alprogram építhető egymásba. A GOSUB utasítás működését a 2. ábra mutatja be.
2. ábra: a GOSUB ciklusok működése
Az IF utasítás tipikus alakjai a következők:
10 IF A=5 THEN GOTO 3
10 IF A=5 AND B=4 THEN GOTO 3
10 IF A$="TEJ" THEN GOSUB 200
10 IF A<>0 THEN GOTO 300
10 IF A THEN GOTO 300
A korábban ismertetett vezérlőutasítások feltétel nélküli ugrásokat hajtottak végre a programban. Az IF utasítás a feltételes ugrásokra, azaz a programon belüli elágazásra alkalmas. Amennyiben az IF-et követő feltétel igaz, a THEN utáni utasítás kerül sorra, amennyiben pedig nem, a program a következő utasításnál folytatódik. A logikai függvények értéke a hamis állítás esetén 0, így az IF utasításban nemcsak logikai függvények szerepelhetnek, hanem tetszőleges algebrai kifejezések. Ha a kifejezés értéke nulla, az utasítás hatástalan, egyébként pedig a THEN utáni utasítás következik. Így a példákban szereplő IF A<>0 és IF A kifejezések azonos hatásúak.
Nagy előnye a ZX81 BASIC nyelvének, hagy az IF utasításban a THEN után tetszőleges utasítás következhet - a korábbi BASIC változatoknál a legtöbb esetben csak GOTO és GOSUB volt megengedve. Ez azt jelenti, hogy ide akár egy újabb IF utasítást is tehetünk, az IF A=B THEN IF C=D THEN GOTO 3 utasítást a gép értelmezi. Ezt azonban a logikai függvények alkalmazásával (IF A=B AND C=D THEN...) egyszerűbben megoldhatjuk.
A korábbi BASIC változatoknál többnyire nem szükséges a THEN szót kiírni, a ZX81-nél azonban elkerülhetetlen, különben nem kerül a gép "K", vagyis utasítás módba.
A FOR utasítás tipikus alakjai a következők:
10 FOR I=1 TO 20
10 FOR I=1 TO 20 STEP 3
10 FOR I=A TO B STEP C
10 FOR I=-1 TO -20 STEP -1
A FOR utasítás - a NEXT utasítással együtt - a programciklusok szervezésére hivatott. A FOR utasítás hatására az utasításban szereplő ciklusváltozó (a példákban I) felveszi a megadott kezdőértéket, majd az un. ciklusmag (a FOR-t követő utasítások) kerül végrehajtósra. A NEXT I utasításhoz érve a program visszaugrik a- FOR utasításra, és a STEP után szereplő lépésközzel növeli, ill. csökkenti a ciklusváltozót. Mindez addig folytatódik, míg a ciklusváltozó el nem éri a TO után megadott határt. Ekkor a program futása a NEXT utáni utasítással folytatódik. A STEP után természetesen negatív szám is szerepelhet, ez esetben a ciklus végét az jelenti, ha a ciklusváltozó kisebb, lesz mint a megadott határ. A ciklusból kilépve a ciklusváltozó már a megváltozott, azaz a következő értéket veszi fel. Így pl.
10 FOR I= 1 TO 10
15 NEXT I
ciklus végrehajtása után I értéke 11.
A ciklus végrehajtósára nem kerül sor, ha a ciklus végét meghatározó feltételek már kezdetben fennállnak. Így pl. ha A=5 és B=6, akkor a FOR I=A TO B STEP -1 hatására a program a NEXT I utasításnál folytatódik.
Fontos megjegyezni, hogy a ciklusváltozó neve csak egy karakterből állhat, pl. a FOR AA=1 TO 5 nem megengedett. Ha a ciklusváltozót eggyel akarjuk növelni, a STEP 1 kiírása fölösleges.
Ha több ciklust rakunk egymásba, mindig ügyelni kell arra, hogy az egyes ciklusok ne keveredjenek. Jó szervezésű ciklusra példa:
FOR I= 1 TO 2
FOR J= 1 TO 2
NEXT J
NEXT I
Ebben a ciklusváltozók sorra felvett értékei a következők: (I=1 J=1); (I=1 J=2); (I =2 J=1); (1=2 J=2). A ciklusból való kilépéskor mindkét ciklusváltozó értéke 3 lesz.
Megcserélve a két NEXT utasítást, rossz szervezésű ciklusokat kapunk, hiszen a ciklusváltozók értékpárjai a következők: (1=1 J=1); (I=2 J=1); (1=3 J=2), vagyis hiányosak. A ciklusból kilépve a változók értéke 4, ill. 3 lesz. Látható hogy a ciklusok kereszteződésekor problémák lépnek fel, így azt minden esetben kerülni kell.
A STOP utasítás hatására a program futása megszakad. Közvetlen utasításként nem alkalmazható.
b) Adatok ki- és bevitele, értékadás
Adatokat a rendszerbe INPUT utasítással vihetünk be. Az utasítás tipikus alakjai:
10 INPUT A
10 INPUT A$
Az INPUT hatására a program futása megszakad, és a számítógép várja, hogy a billentyűzeten keresztül egy új adat, és a NEW LINE kerüljön beütésre. Ezalatt a képernyőn az "L" kurzor látható. Az INPUT változója felveszi a beütött értéket, majd folytatódik a program futása. Ha a gép string változót vár, az "L" idézőjelek között jelenik meg.
Adatbeadóskor nemcsak számot, ill. stringet adhatunk meg, hanem bármilyen korábban értelmezett változót vagy algebrai kifejezést is. Pl., ha a program az INPUT A utasítás hatására áll le, akkor a B NEW LINE leütésének hatásara az A változó B értékét veszi fel. Ugyanígy adhatunk be korábban értelmezett szövegváltozót is, ekkor azonban a képernyőn látható idézőjeleket RUBOUT-tal törölni kell.
A PRINT utasítás tipikus alakjai a következők:
10 PRINT A
10 PRINT "A= ";A
10 PRINT 2*A+B,C
10 PRINT AT 5,6;X;TAB 20;Y
A PRINT utasítás hatására a ZX81 kiírja a képernyőre az utasításban szereplő változó értéket, és az idézőjelek között szereplő karaktereket. A különböző változókat ";" és karakterekkel választhatjuk el egymástól. Ha az elválasztásra "," karaktert használunk, a kiírás az adott sor második felében, ill. ha a kiírás már túljutott a sor felezőpontján, a következő sor első helyén folytatódik. Ha az elválasztás ";" karakterrel történik, a nyomtatás folyamatos. A ZX81 minden PRINT utasítás végén "rááll" a következő sor elejére, kivéve, ha az előző PRINT utasítás ";" vagy "," karakterekkel zárult. Így - az említettek kivételével minden PRINT utasítás hatására az információk kiírása új sorba történik.
A képernyőn egy sorba 32 karaktert nyomtathatunk (a pozíciók sorszáma 0-31 , balról jobbra), és 22 sorba írhatunk (a sorok fentről lefelé vannak számorva, 0-21-ig).
A PRINT utasítást kiegészíthetjük a TAB és az AT szavakkal. A TAB N hatására a kiírás az adott sor N-edik pozíciójában, ill. ha az adott sor kiírása már túljutott az N. pozíción, a következő sor N-edik. pozíciójában folytatódik. A TAB után természetesen bármilyen változót vagy kifejezést is írhatunk. Ha a változó értéke nem egész szám, akkor a gép a kerekített értéket veszi figyelembe. Ha a változó
értéke több mint 31, akkor a 32-vel való osztás után kapott maradéknál eggyel kisebb pozíciójú oszlopban jelenik meg a kiírás (32=0;,33=1; stb).
A PRINT AT N,M utasítás hatásara a nyomtatás az N-edik sor M-edik pozíciójába kerül. N és M helyén itt is állhatnak változók, de a kerekítés után értékük a 0 <= N <= 21, ill. a 0 <= M <= 31 határok közé kell essen, ellenkező esetben ugyanis hibaüzenetet kapunk.
Az LPRINT utasítás működésbe hozza a nyomtatót, egyébként formailag megegyezik a PRINT utasítással.
A PLOT utasítás rajzolásra alkalmas, tipikus alakjai a következők:
10 PLOT 15,32
10 PLOT X,3*Y
Az utasítás segítségével egy pontot rakhatunk a képernyőre. A PLOT utasítással a képernyő 44x64 pontra bontható fel, ami nem nevezhető finom felbontású grafikának! A PLOT X,Y utasítás hatására a képernyő aljától Y, bal szélétől X távolságú pontot jelöljük ki, ugyanis az origó a bal alsó sarokban van. Ha X és Y az esetleges kerekítés után kívül esik a megadott határokon, hibajelzést kapunk. A ZX81 a különböző betű- és egyéb karakterek kinyomtatásakor ennél négyszeresen jobb felbontást használ, ez a felbontóképesség azonban közvetlenül nem érhető el.
Az UNPLOT X,Y utasítással az X,Y koordinátájú pontot kioltjuk, ha fel volt gyújtva, egyébként üres helyen nincs hatása. Ha az adott ponton bármilyen más karakter van, az utasítás az egész karaktert eltünteti, tehát az UNPLOT utasítással nem lehet fél karaktereket előállítani.
A COPY utasítás lemásolja a nyomtatóra a képernyőt, persze csak akkor, ha az be van kötve, egyébként hatástalan.
Az értékadáskor elengedhetetlen a LET utasítás, aminek tipikus alakjai a következők:
10 LET A=5
10 LET A=B
10 LET A=5+A*3
10 LET A$="COMPUTER"
A LET utasítás hatására-az egyenlőség bal oldalán szereplő változó a jobb oldalon lévő kifejezés értékét veszi fel (tehát a LET A=B nem azonos a LET B=A utasítással). A LET utasítás szövegváltozók megadására is alkalmas. Pl, ha az A$ változó értéke "VARJU", akkor a LET A$(1 TO 3)="BOR" utasítás hatására A$ értéke "BORJU" lesz.
c) Parancs típusú utasítások
A parancs típusú utasítások többnyire közvetlen utasításként használatosak, de programban is alkalmazhatók.
A RUN utasítás szokásos alakjai a következők:
RUN
RUN 200
10 RUN 200
A RUN utasítás hatására fut a program az első ill. a megadott sorszámú utasítástól kezdődően. A RUN hatására a számítógép törli a képernyőt és az összes változót.
A program futása a BREAK (Shift és Space) lenyomásával szakítható meg.
A CLEAR utasítás törli az összes változót és a képernyőt, a program változatlan marad.
A NEW utasítás törli a BASIC programot, az összes változót és a képernyőt.
A CLS (CLEAR SCREEN) utasítás csak a képernyőt törli, a program és a változók megmaradnak.
A CONT (CONTINUE) utasítás a megszakított programfutás újraindítására szolgál. Hatására a program onnan folytatódik, ahol a megszakítás történt (ha a futás STOP utasítás hatására szakadt meg, a végrehajtás a következő utasításnál folytatódik). Ugyancsak CONT utasítást kell adni, ha a program futása azért állt le, mert a képernyő betelt.
Bár már említettük, a teljesség kedvéért itt is felsoroljuk a SAVE-LOAD utasításokat.
A SAVE utasítás tipikus alakja a következő:
SAVE "PROGRAM"
100 SAVE "PROGRAM"
100 SAVE B$
A SAVE utasítás hatására a ZX81 az idézőjelek közé irt néven, a magnetofon segítségével szalagra írja a programot, az összes változó értékét, és a képernyőt. Ha a program futását megszakítjuk, és a programot magnetofonon rögzítjük, akkor később a programot visszaolvasva a CONT utasítással a megszakítás helyéről folytatható a program futtatása. Így lehet kimenteni akár egy félbehagyott sakkpartit is. Ha az utasítást a programban helyezzük el, a beolvasás után a program azonnal futni, fog, a SAVE utasítás utáni utasítástól. A program neve helyett szövegváltozó is szerepelhet.
A LOAD utasítás tipikus alakjai a következők:
LOAD "PROGRAM"
LOAD ""
100 LOAD "PROGRAM"
100 LOAD B$
Az utasítás hatására a ZX81 beolvassa az idézőjelek közötti néven elmentett programot és összes változóját a magnetofonról. Ha nem akarjuk, hogy a változók elvesszenek, akkor a programot RUN helyett GOTO 1-gyel indítsuk. Ha a program neve helyett csak két idézőjelet írunk, akkor a szalag következő programját olvassa be a gép.
A LIST utasítás hatására a ZX81 kilistázza a programot a képernyőre. Ha a LIST után egy számot, vagy változót írunk (pl. LIST 100), akkor az adott sorszámú utasítástól kezdve kapjuk a listát.
Az LLIST a nyomtatóra írja ki a listát, ha az nincs bekötve, az utasítás hatástalan.
d) Egyéb utasítások
A REM (REMARK) utasítás hatására ugyan nem történik semmi, az utasítás mégis hasznos, mert módot ad arra, hogy a programozó megjegyzéseket tegyen a programlistába. Az utasítás egyéb információ tárolására is szolgálhat, erről később lesz szó.
A PAUSE utasítás tipikus alakjai:
20 PAUSE 200
20 PAUSE 4E4
Hatására a program futásában a megadott időtartamra szünet következik be. Az időtartamot 1/50 másodpercekben mérjük (tehát a PAUSE 50 utasítás 1 másodperces szünetet jelent). Az időtartamul megadott számnak 2^16-nál (65536) kisebbnek kell lennie, ellenkező esetben hibajelet kapunk. Ha a megadott szám 2^15-nél (32768) nagyobb a ZX81 már nem méri a szünet idejét (az amúgy is 10 percnél hosszabb lenne). A szünet a gép bármelyik billentyűjét megérintve feloldható. Sajnos, a PAUSE utasítás használatakor a kép kicsit megremeg, így túl gyakran ne használjuk. Gyakran alkalmaznak PAUSE helyett üres ciklusokat (FOR I=1 TO 100 / NEXT I), ez azonban memóriaigényes.
A SCROLL utasítás hatására a képernyő felső sora eltűnik, az összes többi sor eggyel feljebb kerül. Így a legalsó sor üressé válik. Ha a SCROLL után PRINT utasítást adunk, a kiírás a legalsó sorba kerül.
A FAST utasítás hatására a ZX81 "gyors" üzemmódba lép, a program futási sebessége mintegy négyszeresére nő. A "gyors" üzemmódban a képernyőről a kép eltűnik, de az ott tárolt információ nem vész el. Ebben az üzemmódban a képernyő csak akkor látható, ha a program lefutott vagy INPUT utasítás hatására áll le. A FAST utasítást a hosszú számítások elvégzésén kívül akkor célszerű alkalmazni, ha hosszú programot gépelünk be a számítógépbe. Ekkor ugyanis nem kell minden egyes utasítás után arra várni, hogy a ZX81 átrendezze a képernyőt.
A SLOW utasítás hatására a ZX81 visszakerül eredeti üzemmódjába, tehát a SLOW megszünteti a FAST hatását.
A RAND (RANDOMIZE) utasítás véletlenszerűen beállítja a véletlenszám-generátor un. "mag" számát, vagyis azt a számot, amelyből az RND függvény a következő számot előállítja. E beállításhoz a ZX81 bekapcsolása óta eltelt idő kerül felhasználásra (1/50 másodperc pontossággal), ami gyakorlatilag véletlennek tekinthető. Vigyázat! Ha a programot szalagról olvassuk be, és az egyből futni kezd (a program futás közben volt SAVE-elve), a RAND utasítás mindig ugyanazt a magszámot állítja be. Ennek az az oka, hogy ekkor a bekapcsolás óta eltelt időt mérő rendszerváltozót is beolvassuk, és a RAND ugyanannyi idő múlva kerül sorra. Ilyenkor szakítsuk meg a programot, és indítsuk újra.
A POKE utasítás tipikus alakja a következő:
10 POKE 17500,237
A POKE utasítással közvetlenül tehetünk adatokat a ZX81 memóriarekeszeibe. Az elsőként megadott szám a memóriarekesz címe, a második a címre kerülő érték. A példában megadott utasítás tehát a 17500 című memóriába 273-at rak. A memóriacímek természetesen csak a RAM tartományba eshetnek, ellenkező esetben ugyanis az utasítás hatástalan. A memóriákba -255 és +255 közötti egész számokat rakhatunk. Ha a megadott szám nem egész, akkor a ZX81 kerekít. A negatív számok tárolásakor a megadott számnál 256-tal nagyobb kerül a memóriába, tehát a FOKE 17500,255 és a POKE 17500,-1 utasítások azonos hatásúak. Ha a beírandó érték a megadott határokon kívül esik, hibajelzést kapunk.
A DIM utasítás tipikus alakjai a következők:
10 DIM A(15)
10 DIM A(4,5,6,2)
10 DIM A8(3,7)
Ha a programban tömbváltozót használunk, azt az első előfordulása előtt definiálni kell. Erre szolgál a DIM utasítás. A tömbváltozók neve csak egy betű lehet (stringváltozók esetén egy betü + $ karakter). A DIM utasítás a definiált vektor, ill. tömb összes elemét nullára (ill. stringváltozó esetén betűközre) állítja be. Így a DIM utasítás a korábban használt tömbök nullázására is alkalmas. Stringváltozókból álló tömb definiálása esetén a megadott indexek közül az utolsó az elemek hosszát határozza meg. Néhány példa:
A DIM A$(7) utasítást adva A$ csak hét karakterből állhat. Az ennél rövidebb értékeket a ZX81 betűközökkel egészíti ki, a hosszabbakból pedig csak az első hét karaktert veszi figyelembe.
A DIM A$(4,8) utasítás egy négyelemű, elemenként nyolc karakterből álló stringvektort definiál. A vektorelemek karakterenként és elemenként kezelhetők. A példában tehát A$(3)=AS(3,1 TO 8). Ennek az a következménye, hogy a stringtömb nevét más szövegváltozókra nem alkalmazhatjuk. Hibás tehát ez a kis program:
10 DIM A$ (4,8)
15 LET A$="ALMA"Itt ugyanis pl. az A$ vektor harmadik elemét, és az A$ változó harmadik karakterét ugyanúgy A$(3)-mal jelölnénk, ami zavart okozna.
Szám jellegű változókra nincsen ilyen megkötés, tehát a10 DIM A(4,8)
15 LET A=15utasítások nem hibásak.
1. példa : Nyelvtanulás (16 K RAM szükséges)
1 PRINT "MIT CSINALJUNK MOST ?"
2 PRINT "1 - UJ SZAVAK BEIRASA "
3 PRINT " (REGI SZAVAK MELLE)"
4 PRINT
5 PRINT "2 - UJ SZAVAK BEIRASA"
6 PRINT " (REGI NINCS,VAGY TORLODIK)"
7 PRINT
8 PRINT "3 - A PROGRAM ES A SZAVAK"
9 PRINT " SZALAGRA IRASA"
10 PRINT
11 PRINT "4 - TANULAS"
12 INPUT Q
13 GOTO 100*Q
100 CLS
110 PRINT "MAGYAR SZO",D$
120 LET I=I+1
130 INPUT A$(I)
140 IF A$(I)=" " THEN GOTO 1
150 PRINT A$(I),
160 INPUT B$(I)
170 PRINT B$(I)
180 GOTO 120
200 RUN 210
210 LET I=0
220 PRINT "MILYEN NYELVEN TANULSZ ?"
230 INPUT D$
240 PRINT "HANY SZO KERUL BEUTESRE ?"
250 PRINT "(A KESOBBI BOVITESEKKEL EGYUTT)"
260 INPUT Q
270 DIM A$ (Q,10)
280 DIM B$ (Q,10)
290 GOTO 100
300 PRINT "KAPCSOLD A MAGNOT FELVETELRE,"
310 PRINT "ES ELLENORIZD A ZSINORT."
320 PRINT "HA KESZEN VAGY, ERINTSD MEG "
330 PRINT "A ZX 81 EGYIK BILLENTYUJET."
340 PAUSE 4E4
350 SAVE "NYELVTESZT"
360 CLS
370 GOTO 1
400 RAND
410 LET JO=0
420 LET ROSSZ=0
430 CLS
440 PRINT "VALASZD KI A TESZT TIPUST"
450 PRINT
460 PRINT "1 - MAGYAR-";D$
470 PRINT "2 - ";D$;"-MAGYAR"
480 PRINT "3 - VEGYESEN"
490 INPUT T
500 CLS
505 LET A=RND*(I-2)+1
510 DIM C$(10)
515 GOTO 500+20*T
520 PRINT AT 7,0;"HOGY IRJAK ";D$;"UL ?"
522 PRINT AT 10,7;A$(A)
524 INPUT C$
526 IF C$="0 " THEN GOTO 650
528 PRINT AT 10,22;C$
530 IF C$=B$(A) THEN GOTO 600
532 PRINT "ROSSZ. A HELYES VALASZ: ",B$(A)
534 LET ROSSZ=ROSSZ+1
536 PAUSE 4E4
538 GOTO 500
540 PRINT AT 7,0; "HOGY IRJAK MAGYARUL ?"
542 PRINT AT 10,7;B$(A)
544 INPUT C$
546 IF C$="0 " THEN GOTO 650
548 PRINT AT 10,22;C$
550 IF C$=A$(A) THEN GOTO 600
552 PRINT "ROSSZ. A HELYES VÁLASZ: ",A$(A)
554 LET ROSSZ=ROSSZ+1
556 PAUSE 4E4
558 GOTO 500
560 IF RND<0.5 THEN GOTO 520
570 GOTO 540
600 PRINT "HELYES VÁLASZ."
610 LET JO=JO+1
620 PAUSE 100
630 GOTO 500
650 CLS
660 PRINT "A TESZT VEGE"
670 PRINT "A KERDESEK SZÁMA: ";JO+ROSSZ
680 PRINT "JO VÁLASZ: ";JO;" (";JO/(JO+ROSSZ); ") SZAZALEK"
690 PRINT
700 IF JO/(JO+ROSSZ)>=0.8 THEN PRINT "IGEN JO EREDMENY."
710 IF JO/(JO+ROSSZ)>=0.6 AND JO/(JO+ROSSZ)<0.8 THEN PRINT "NEM TUL JO EREDMENY."
720 IF JO/(JO+ROSSZ)<0.6 THEN PRINT "GYAKOROLJ TOVÁBB."
730 PAUSE 4E4
740 GOTO 1
Az első példaprogramunk a nyelvtanulóshoz ad segítséget. A beütött magyar-idegen nyelvű szó- (kifejezés-) párokat kérdezi ki, véletlenszerű sorrendben. A programban használt változók jelentése a következő:
A$ tömb | - a magyar szavak jegyzéke |
B$ tömb | - az idegen nyelvű szavak jegyzéke |
Q | - INPUT változó, értelme változik |
D$ | - az idegen nyelv neve |
JO, ROSSZ | - a jó ill. a rossz válaszok száma |
I | - a beírt szópárok száma |
C$ | - a válasz |
3 ábra: A nyelvtanulás program folyamatábrája
A folyamatábrán látható, hogy az indítás után választanunk kell, új szavakat írunk be (a régieket kiegészítve vagy helyettük), a beirt szavakat szalagra írjuk vagy megtanuljuk. A program első használatakor természetesen csak az új szavak beírását választhatjuk ("2" a menüből). A beírható szavak számát a rendelkezésre álló memória nagysága határozza meg, 8K memóriabővítés esetén is több száz szó elhelyezhető. A program max. 10 betűs szavak, kifejezések tárolásara készült, amennyiben hosszabb kifejezéseket akarunk tanulni a 270-280 sorokban lévő utasításokat kell megváltoztatni. Az utolsó szó beütése után - az új szó helyett - a NEW LINE billentyűt kell lenyomni, erre ismét választhatunk a program szolgáltatásai közül.
A tanuláshoz háromféle módszer választható. A gép kiír magyar nyelvű kifejezéseket, és az idegen nyelvű megfelelőt kérdezi; az idegen nyelvű kifejezések magyar megfelelőjét kérdezi, vagy vegyesen alkalmazza a két kérdéstípust. A teszt végén a válasz helyett a "0" gombot kell megnyomni. Ekkor a gép értékeli a teljesítményt.
Természetesen az ehhez hasonló programok esetében, ahol korábban beütött adathalmazt használunk, nem szabad a programot RUN utasítással újraindítani, hiszen ezzel az összes változót törölnénk; GOTO 1 utasítás eseten a változók megmaradnak.
A program szervezésénél megfigyelhető, hogy előre került a menü, amiből kiválaszthatjuk, mit csináljon a program. Ha a programot a saját szalagra író részével vesszük fel a kazettára, a beolvasás után azonnal futni kezd, és - lévén a SAVE utasítás után a programban GOTO 1 szerepel - a képernyőn egyből a funkcióválasztás jelenik meg.
2. példa: Kis üzlet könyvelése (16 K RAM szükséges)
1 CLS
2 PRINT "FUNKCIO VALASZTAS"
3 PRINT " 1 -- PENZTARGEP"
4 PRINT " 2 -- LELTAR"
5 PRINT " 3 -- UJ ARU BEIRASA"
6 PRINT " 4 -- ESTI ZARAS, ADATOK "
7 PRINT " 5 -- SZALAGRA IRASA"
8 INPUT Q
9 GOTO 1000*Q
1000 LET KASSZA=0
1005 CLS
1014 LPRINT "DB ARU KOD EGYS.AR AR"
1015 PRINT "DB ARU KOD EGYS.AR AR"
1020 LPRINT
1030 LET SUM=0
1040 PRINT AT 20,0;"KOD: ";
1050 INPUT K
1060 IF K=0 THEN GOTO 1200
1070 IF K=9999 THEN GOTO 1
1080 PRINT AT 2,4;A$(K);TAB 13;K;TAB 19;A(K)
1090 PRINT AT 20,5;"DARABSZAM: "
1100 INPUT D
1110 IF D=0 THEN GOTO 1030
1120 PRINT AT 2,0;
1130 LPRINT D;TAB 4;A$(K);TAB 13;K;TAB 18;A(K);TAB 27;D*A(K)
1135 PRINT D;TAB 4;A$(K);TAB 13;K;TAB 18;A(K);TAB 27;D*A(K)
1140 LET D(K)=D(K)-D
1150 LET SUM=SUM+D*A(K)
1160 GOTO 1040
1200 LPRINT
1210 LPRINT "FIZETENDO : ";SUM
1215 PRINT "FIZETENDO : ";SUM
1220 LET KASSZA=KASSZA+SUM
1230 GOTO 1005
2000 REM LELTAR
2010 LPRINT "KESZLET"
2020 LPRINT
2030 LPRINT
2040 LPRINT
2050 LPRINT "KOD ARU KESZLET E.AR ERTEK"
2060 LPRINT
2070 LET SUM=0
2080 FOR K=1 TO I
2090 LPRINT K;TAB 5;A$(K);TAB 13;D(K);TAB 20;A(K);TAB 26;D(K)*A(K)
2100 LET SUM=SUM+A(K)*D(K)
2110 NEXT K
2120 LPRINT
2130 LPRINT "OSSZES ARU ERTEKE: ";SUM
2140 GOTO 1
3000 REM UJ ARU
3010 CLS
3020 PRINT "REGI KESZLET VAN ? (I/N) "
3030 INPUT Q$
3040 IF Q$="N" THEN GOSUB 3500
3050 CLS
3060 PRINT "KOD ARUNEV DARABSZAM E.AR"
3070 PRINT " REGI UJ"
3080 INPUT L$
3090 IF L$="0" THEN GOTO 1
3100 IF L$="" THEN GOTO 3400
3110 IF VAL L$>=I THEN GOTO 3400
3120 LET L=VAL L$
3130 PRINT AT 3,0;L;TAB 4;A$(L);TAB 14;D(L)
3140 INPUT D
3150 LET D(L)=D(L)+D
3160 PRINT TAB 20;D(L);TAB 24;A(L)
3165 PAUSE 4E4
3170 GOTO 3050
3400 LET I=I+1
3410 LET L=I
3420 PRINT AT 21,0;"ARU NEVE: "
3430 INPUT A$(L)
3440 PRINT AT 21,0;"EGYSEG AR: "
3450 INPUT A(L)
3460 PRINT AT 21,0;" "
3470 GOTO 3110
3500 DIM A(300)
3510 DIM A$(300,8)
3520 DIM D(300)
3530 LET I=0
3540 RETURN
4000 REM NAPI ZARAS
4010 PRINT "NAPI BEVETEL :";KASSZA
4020 PRINT "KAPCSOLD FELVETELRE A MAGNETOFONT,"
4030 PRINT "ELLENORIZD A KABELT."
4040 PAUSE 4E4
4050 SAVE "KONYVELES"
4060 GOTO 1
A program segítségével egy kisebb bolt teljes könyvelése ellátható. Ehhez természetesen memóriabővítés feltétlenül szükséges (legalább 16K), és ha azt akarjuk, hogy a gép blokkot is adjon, nyomtató is kell.
A program változóinak jelentése a következő:
A(300) | - az árucikkek egységára |
A$(300,8) | - az árucikkek neve |
D(300) | - darabszám |
I | - az elkönyvelt árucikkek száma |
SUM | - a vevő által fizetendő összeg, leltárnál a teljes készlet értéke |
KASSZA | - a napi forgalom |
A program használata esetén minden árufajtának egy kódszámot kell adni (példánkban 0-300). Az áru érkezésekor az áru neve, egységára és mennyisége a memóriába ill. a magnetofonszalagra kerül. Amennyiben van már a raktáron azonos áruból (azonos kódszámon), csak az újonnan érkezett mennyiséget kell beírni. Ha a kódszám helyett NEW LINE-t írunk, automatikusan a következő kódszám jelenik meg, akárcsak a túl nagy új kódszám beütésekor. Az új árúk könyvelése a "0" kódszám beírásával fejezhető be. Eladáskor a kódszám beütésére megjelennek a képernyőn a szükséges adatok. Több tételből álló vásárlás esetén a ZX81 elvégzi a szükséges összeadásokat és szorzásokat, a megfelelő értékek a blokkon is megjelennek. A program könyveli a napi forgalmat, és azt, hogy az egyes árucikkekből mennyi van még raktáron. A leltár funkció felsorolja a teljes készletet a megfelelő adatokkal.
A "PENZTARGEP" és az "UJ ARU" funkciók működése a folyamatábrákon is megfigyelhető.
4. ábra: A könyvelő program "PÉNZTÁRGÉP" blokkjának folyamatábrája
5. ábra: A könyvelő program "ÚJ ÁRU" blokkjának folyamatábrája
3. példa: Memória társasjáték (8 K RAM szükséges)
10 RAND
15 LET V=26
20 CLS
25 DIM A(10,10)
30 PRINT "JATEKOSOK SZAMA:"
35 INPUT JAT
40 DIM P(JAT)
45 DIM J$(JAT,7)
50 PRINT "ZX81 JATSZIK?"
55 INPUT L$
60 IF L$(1)<>"I" THEN GOTO 85
65 LET J$(1)="ZX81"
70 PRINT "NEHEZSEGI FOK?"
75 INPUT N
80 PRINT N
85 FOR I=1 TO JAT
90 PRINT I;". JATEKOS NEVE:";
95 IF I=1 AND L$(1)="I" THEN GOTO 105
100 INPUT J$(I)
105 PRINT J$(I)
110 NEXT I
115 GOSUB 1000
120 FOR I=1 TO JAT
125 GOSUB 2000
130 NEXT I
135 GOTO 120
1000 CLS
1010 FOR I=38 TO 63
1020 FOR J=1 TO 2
1030 LET X=INT (10*RND)+1
1040 LET Y=INT (10*RND)+1
1050 IF A(X,Y) THEN GOTO 1030
1060 LET A(X,Y)=I
1070 NEXT J
1080 NEXT I
1085 FOR X=1 TO 10
1090 FOR Y=1 TO 10
1100 IF A(X,Y) THEN PRINT AT (X-1)*2+1,(Y-1)*2+1;""
1110 NEXT Y
1120 PRINT AT (X-1)*2+1,20;X
1130 NEXT X
1140 PRINT AT 0,0;" A B C D E F G H I J"
1150 RETURN
2000 PRINT AT 0,22;J$(I)
2010 PRINT AT 1,22;"KOVETKEZIK"
2020 IF I=1 AND L$(1)="I" THEN GOTO 3000
2030 GOSUB 2200
2040 LET C=INT A(X,Y)
2050 LET X1=X
2060 LET Y1=Y
2070 GOSUB 2200
2080 PAUSE 4E4
2090 IF INT A(X,Y)=C AND 10*X+Y<>10*X1+Y1 THEN GOTO 2300
2100 PRINT AT (X-1)*2+1,(Y-1)*2+1;""
2110 PRINT AT (X1-1)*2+1,(Y1-1)*2+1;""
2120 RETURN
2200 INPUT B$
2210 LET Y=CODE B$(1)-37
2220 IF Y>10 OR Y<1 THEN GOTO 2200
2230 INPUT X
2240 IF X>10 OR X<1 THEN GOTO 2230
2250 PRINT AT (X-1)*2+1,(Y-1)*2+1;CHR$ A(X,Y)
2260 IF INT A(X,Y)=A(X,Y) THEN LET A(X,Y)=A(X,Y)+0.1
2270 RETURN
2300 IF C<1 THEN GOTO 2000
2305 IF I=1 THEN PAUSE 150
2310 LET P(I)=P(I)+1
2320 LET A(X1,Y1)=0
2330 LET A(X,Y)=0
2340 PRINT AT (X-1)*2+1,(Y-1)*2+1;" "
2350 PRINT AT (X1-1)*2+1,(Y1-1)*2+1;" "
2360 LET V=V-1
2370 FOR J=1 TO JAT
2380 PRINT AT J+4,22;J$(J);" ";P(J)
2390 NEXT J
2400 IF V=0 THEN STOP
2410 PRINT AT 3,22;"EREDMENY"
2420 GOTO 2000
3000 LET Y1=11
3010 LET X1=11
3020 GOSUB 3200
3030 LET X1=X
3040 LET Y1=Y
3050 LET C=INT A(X,Y)
3060 PRINT AT (X-1)*2+1,(Y-1)*2+1;CHR$ A(X,Y)
3070 FOR J=1 TO 10+3*N
3080 GOSUB 3200
3090 IF INT A(X,Y)=C AND A(X,Y)>C THEN GOTO 2300
3100 NEXT J
3120 PRINT AT (X-1)*2+1,(Y-1)*2+1;CHR$ A(X,Y)
3130 IF A(X,Y)=C THEN GOTO 2300
3140 PAUSE 4E4
3150 GOTO 2100
3200 LET X=INT (10*RND)+1
3210 LET Y=INT (10*RND)+1
3220 IF A(X,Y)< 1 THEN GOTO 3200
3230 IF X=X1 AND Y=Y1 THEN GOTO 3200
3240 RETURN
9000 SAVE "MEMORIA"
9005 GOTO 1
A memóriaprogram az ismert "memória" társasjáték számítógépes változata. A játékosok számának a program gyakorlatilag nem szab határt (a képernyőn akár 17 játékos eredményének könyvelésére is van hely), de 6-7 játékosnál többnek nem érdemes játszania. A játékban a ZX81 egy 10x10-es négyzethálón 26 betűpárt helyez el. A képernyőn csak az látszik, hogy melyik mezők "élnek" a játékban, de hogy milyen betűt rejt, az titok. A játékosok sorra következnek, és tippelhetnek arra, hogy melyik két ponton található azonos betű (p1. A1-B8). A tippre válaszként a gép megmutatja a két mezőn elrejtett betűket. Ha talált, vagyis a betűk azonosak, a két mező eltűnik a képernyőről, a játékos pedig szerez egy pontot, majd ismét ő következik. Ha rossz a tipp (a két betű különbözik), akkor a játékosok megszemlélhetik és megjegyezhetik a betűket, majd bármelyik billentyű megérintése után a betűk eltűnnek (a pontok természetesen megmaradnak). Ezután a következő játékos jön. Az egyik játékos lehet a ZX81 is, előre megválasztható ügyességi fokozattal.
A felhasznált változók jelentése a következő:
A(10,10) - a memorizálandó betűk kódjait és helyét tartalmazó mátrix N - a nehézségi fok JAT - a játékosok száma P(I) - a játékosok pontszámai J$(I) - a játékosok nevei X, Y - koordináták X1, Y1 - a tipp első pontjának koordinátái C - a tipp első pontjának kódja B$ - a tipp X koordinátája betű alakban (A-J) V - a még élő pontok száma I, J - ciklusváltozók
A program szervezését a folyamatábra is mutatja. A vezérlő blokk a kezdeti adatok bekérése után elvégezteti a keverést, majd sorra jönnek a játékosok egészen a játék végéig.
6. ábra: A memória társasjáték program folyamatábrája
A keverés úgy történik, hogy a ZX81 sorra veszi az ábécé betűinek megfelelő kódokat (38-63), mindegyiket kétszer. A számokat egy 10x10-es mátrix elemeiként véletlenszerűen helyezi el. Természetesen megvizsgálja azt is, hogy a kiválasztott hely foglalt-e; ha igen, új helyet keres neki. A ciklus végén a mátrix 100 eleme közül csak 52 különbözik nullától. Ezután a nullától különböző pontoknak megfelelő helyre egy "" karakter kerül a képernyőre.
A játékosok tippjeit vizsgáló blokk először azt nézi meg, a ZX81 következik-e soron (2020. utasítás). Ha igen, a 3000. utasítás végrehajtása következik. Ha nem, a 2200-as szubrutin megkérdezi a játékos tippjeit, a megfelelő mezőkön láthatóvá válnak a betűk (2250. utasítás). Ilyenkor a betűknek megfelelő mátrixelemek értéke az első alkalommal 0.1-del megnő, amire a ZX81 játékához van szükség. A 2090. utasítás megvizsgálja, hogy a kijelölt két betű megegyezik-e. Az utasításban szereplő 10*X+Y<>10*X1+Y1 feltétel védelmet ad az ellen, ha az egyik játékos véletlenül ugyanazt az elemet adja meg tippjében (pl. A7-A7). Ha rossz a tipp, visszakerül a mezőre a "" karakter, és a vezérlőblokk a következő játékost hívja. A jó tippet a 2300. utasítás vizsgálja, megnézi, hagy nem két üres hely beírása miatt állt-e elő a mátrixelemek egyezése. Valódi jó tipp esetén növeli a játékos pontszámát eggyel, majd a 2320-2390 utasítások az új állásnak megfelelően korrigálják a képernyőt. A 2400 utasítás megvizsgálja, vége van-e a játéknak. Ha nincs, a vezérlés visszatér a 2000. utasításra, és a játékos újra tippelhet.
A ZX81 tippjeit a 3000-3240 utasítások állítják elő. Először az Y1 és X1 változók felveszik a 11 értéket (rögtön meglátjuk miért), majd egy véletlenül kiválasztott mátrixelem lesz a tipp első mezője (3200-3240). Mivel ugyanezek az utasítások állítják elő a második mezőt is, a 3230. utasítás megvizsgálja, hogy a generált X,Y koordináták nem azonosak-e az első pont X1, Y1 koordinátáival. Ezért kellett az első pont generálása előtt az X1 Y1 változókat nem létező értékekre beállítani. A kapott értékek elraktározása és a tipp első mezőjének kiivása után a ZX81 gondolkozni kezd: az ügyességi fokozattól függő hosszúságú ciklus véletlenszerűen "bepillant a kártyákba". Ha megtalálja a keresett elem párját, megnézi, hogy az adott elem látható volt-e korábban, vagy sem. Ha nem, továbbmegy, ha igen, a jó tippet elkönyvelő 2300-2420 utasítások végrehajtása következik. Ha a cikluson belül nem talál jó párt az első mezőhöz, akkor a ciklusban utolsót teszi meg tippül. A 3130. utasítás megvizsgálja, hogy nem azonosak-e véletlenül az így kapott mátrixelemek, ami akkor fordulhat elő, mikor a második elem még nem szerepelt korábban, és ezért a cikluson belül a ZX81 még nem tekintette jó párnak. Visszaugorva a 2100. sorra a már ismert utasítások következnek (a pontok eltakarása).
Sokaknak talán nem tűnik korrekt megoldásnak, hogy a ZX81 megvizsgálja a memorizálandó mátrix elemeit, tehát olyan információt is felhasznál, ami a többi játékos előtt rejtve van. Gondoljuk azonban el: ugyanehhez a végeredményhez vezetne az a nem kifogásolható eljárás is, ha a ZX81 külön memóriákban
tárolná a már láthatóvá vált elemek adatait, és onnan venné tippjeit. Ez azonban túlságosan memóriaigényes lenne, és a programot is megnyújtaná. Azzal, hogy a ZX81 megvizsgálja, hogy a talált elem látható volt-e korábban, eleget tesz a fair play szabályainak.
III. További példák és trükkök
III./1. Az INKEY$ használata
A mikroszámítógépek BASIC nyelvei számos új lehetőséget nyitnak meg a programozó előtt. Az egyik ilyen, hogy a legtöbbjük, így a ZX81 is, érzékeli, ha a program futása közben valamelyik billentyűjét megérintik. Pontosabban: bármilyen program fut, a processzor 50-ed másodpercenként automatikusan megnézi, hogy nem nyomták-e meg a BREAK gombot, de beiktatható a programba is olyan utasítás, ami a billentyűket figyeli. Tehát beavatkozhatunk a program futásába anélkül, hogy megszakítanánk.
A program lefutásához szükséges információkat, ill. adatokat a korábban megismert módszer szerint INPUT utasítással vittük a rendszerbe. Az INPUT utasítás hatására a program várakozik, és csak az adat + a NEW LINE gomb lenyomása után folytatódik. Menet közben az INKEY$ (FUNCTION 8) teszi lehetővé a beavatkozást. Az INKEY$ - mint ahogy azt az $ karakter is jelzi - string értéket adó függvény, argumentuma viszont nincs. A programban szereplő INKEY$ értéke az a karakter lesz, amit a program futása közben megnyomunk ("L" szerint). Az INKEY$ függvénnyel csak egy karaktert értelmezhetünk, de megengedett a SHIFT + másik billentyű lenyomása is.
Ha abban a pillanatban, amikor a program az INKEY$ függvényt tartalmazó utasításon áthalad, éppen egyik billentyű sincs lenyomva, akkor az INKEY$ az üres string ("") értéket veszi fel. Mivel az INKEY$ értéke megváltozhat az alatt az idő alatt is, amíg a program a következő utasítást veszi, célszerű minél előbb stabil változóvá alakítani. Ha pl. azt akarjuk, hogy az "A" lenyomására A$ értéke "ALMA", "B"-re "BARACK" és "C"-re "CITROM" legyen, akkor látszólag jó a következő megoldás:
10 PAUSE 4E4
20 IF INKEY$="A" THEN LET A$="ALMA"
30 IF INKEY$="B" THEN LET A$="BARACK"
40 IF INKEY$="C" THEN LET A$="CITROM"
A gyakorlatban azonban, ha pl. a "C" gombot nyomjuk le, és gyorsan elengedjük, a program még nem ér el a 40. sorhoz, és mire az sorra kerül, INKEY$ értéke "" lesz, A$ pedig nem lesz "CITROM". Többnyire legjobb az információt a LET A$=INKEY$ utasítással vinni be a rendszerbe, és a továbbiakban az A$ változót használni a program irányítására. Az előző példa helyesen így néz ki:
10 PAUSE 4E4
15 LET B$=INKEY$
20 IF BS="A" THEN LET A$="ALMA" stb.
A függvény alkalmazásakor ügyelni kell még arra is, hogy az INKEY$ függvényt tartalmazó utasítás végrehajtására ne kerüljön sor közvetlenül, vagy néhány lépéssel a program indítása után. Ekkor ugyanis fennáll annak a veszélye, hogy a program indításához megnyomott billentyűt (RUN, GOTO, NEW LINE) nem engedjük el elég gyorsan, és így azt az INKEY$ függvénnyel is értelmezi a gép. Ha a programot nem tudjuk úgy szervezni, hogy az INKEY$ máshova kerüljön, akkor az indítás után egy késleltető üres ciklust helyezzünk el:
10 FOR I=1 TO 200
20 NEXT I
Erre a PAUSE utasítás nem alkalmazhatd, hiszen az bármelyik billentyűvel feloldható. Az INKEY$ függvény használatának megértéséhez nézzük a következő egyszerű példát:
Példa: Mimóza program
10 IF INKEY$="" THEN CLS
20 IF INKEY$="" THEN GOTO 20
30 LET A$=INKEY$
40 PRINT AT 10,0;"MEGINT ZAVARNAK. ";A$;"GOMBOMAT"
50 PRINT "MEGNYOMTA VALAKI."
60 GOTO 10
Hogyan működik a program? Amíg meg nem érintjük valamelyik billentyűt, a 20. sor ismétlődik. Amint azonban bármit is leütünk, a 40.-50. sor lép működésbe, majd amikor elengedjük, minden törlődik a képernyőről (10. sor), és újra a 20. soron való veszteglés következik.
Az előző programban az "információt" az N IF INKEY$="" THEN GOTO N utasítás segítségével vittük be. Ezt az utasítást akkor kell alkalmazni, amikor azt akarjuk, hogy a program mindenképpen várja meg az információt. Ez a beviteli mód hasonlít az INPUT utasításhoz, csupán a NEW LINE billentyű lenyomását takarítjuk meg. Az INKEY$ függvényt viszont elsősorban más jellegű problémák megoldására érdemes alkalmazni.
Példa: Gépíróteszt
10 LET B=0
15 RAND
20 FOR I=1 TO 100
25 CLS
30 LET A=INT (RND*26)+38
35 PRINT AT 10,15; CHR$ A
40 PAUSE 10
45 IF INKEY$=CHR$ A THEN LET B=B+1
50 NEXT I
55 PRINT "A 100 LEUTESBOL ";B;" VOLT HELYES."
60 STOP
A program 100 betűt villant fel egymás után. A résztvevőnek a betű megjelenése után 0.2 másodpercen belül le kell ütnie a megfelelő billentyűt. A program 10.-15. sora a kezdeti feltételeket állítja be. A 30. sorban szereplő utasítás egy 38 és 63 közé eső véletlen egész számot generál, amire azért van szükség, mert az ábécé betűinek kódja e számok közé esik. A CHRS(A) string-függvény értéke egy betű, ami a 35. sor hatására megjelenik a képernyőn, majd 0.2 másodperc elteltével a 45. sorban az eredmény nyilvántartására kerül sor, vagyis a gép megvizsgálja, hogy a megfelelő gombot nyomták-e le. Ha a leütés hibátlan, az eredményt könyvelő B változó értéke eggyel nő.
Példa: Vonalrajzoló (8 K RAM szükséges)
10 DIM C(64,44)
20 LET A$="8"
30 LET X=32
40 LET Y=22
50 LET D=0
60 CLS
70 IF C(X,Y)=1 THEN GOTO 180
80 LET C(X,Y)=1
90 PLOT X-1,Y-1
100 LET B$=INKEY$
110 IF B$="5" OR B$="6" OR B$="7" OR B$="8" THEN LET A$=B$
120 IF X<64 AND A$="8" THEN LET X=X+1
130 IF X>1 AND A$="5" THEN LET X=X-1
140 IF Y<44 AND A$="7" THEN LET Y=Y+1
150 IF Y>1 AND A$="6" THEN LET Y=Y-1
160 LET D=D+1
170 GOTO 70
180 PRINT AT 10,0;"A JATEKNAK VEGE. A VONAL HOSSZA:",D
190 STOP
A programmal folyamatos, állandóan hosszabbodó vonalat húzunk. A cél: minél hosszabb vonalat húzni anélkül, hogy ugyanazon a ponton többször áthaladnánk. A programban használt változók jelentése a következő:
C(64,44) - a pontok koordinátái; D - a vonal hossza; A$ - irányító; X,Y - a vonal "fejének" koordinátái.
A 20-60. utasítások a kezdeti feltételeket állítják be. A 70. utasítás vizsgálja, hogy az adott pontot a vonal érintette-e már korábban. A C mátrix helyett lehetne közvetlenül a képernyő adatait tároló D-FILE memóriatartományt használni - erről a módszerről e fejezet következő pontjában lesz szó. A 110. utasítás megvizsgálja, hogy az INKEY$ függvény az irányító gombok értékét veszi-e fel. Ha nem, marad az előző irány. A 120-150. utasítások a következő pont koordinátáit számítják ki. E négy utasítást lehetne a
LET X=X+(A$="8" AND 64>X)-(A$="5" AND X>1)
LET Y=Y+(A$="7" AND 44>Y)-(A$="6" AND Y>1)
utasításokkal helyettesíteni. Az ilyen memóriatakarékos módszerekről e fejezet 5. pontjában lesz még szó. A vonal hosszának növekedését a 160. utasítás könyveli el.
III./2. Animáció
A programozás során számtalanszor előfordul, hogy egy ábrát vagy egy szöveget kell mozgatni a képernyőn. Az egész kép felfelé mozgatására van ugyan utasítósunk (SCROLL), de ha csak egyes részeket akarunk mozgatni, és nem csak felfelé, akkor a SCROLL semmiképp nem alkalmas. A vízszintes és függőleges irányú mozgatásra tekintsük a következő programot.
Futóvadlövés (8 K RAM szükséges)
5 LET LOVEDEK=10
10 LET PONT=0
15 LET B$="PONTSZAMOD "
20 LET X=15
25 LET A$=" "
30 LET I=0
35 LET LOVEDEK=LOVEDEK-1
37 CLS
40 PRINT AT 20,X;" I "
45 GOSUB 400
50 IF INKEY$="Z" AND X>0 THEN LET X=X-1
55 GOSUB 400
60 IF INKEY$="M" AND X<30 THEN LET X=X+1
65 GOSUB 400
70 IF INKEY$="X" THEN GOTO 100
75 GOSUB 400
80 GOTO 40
100 FOR J=1 TO 21
105 SCROLL
110 GOSUB 400
115 NEXT J
120 IF I+X=32 THEN GOTO 200
125 IF LOVEDEK=0 THEN GOTO 300
130 GOTO 35
200 PRINT AT 10,13;"TALALT"
205 PAUSE 50
210 LET PONT=PONT+1
215 IF LOVEDEK=0 THEN GOTO 300
220 GOTO 35
300 CLS
305 LET B$(13 TO 15)=STR$ PONT
310 PRINT AT 10,0;B$(I+1 TO 32);B$(1 TO I)
315 LET I=I+1
320 IF I=32 THEN LET I=0
325 IF INKEY$="U" THEN RUN
330 GOTO 310
400 PRINT AT 0,0;A$(I+1 TO 32);A$(1 TO I)
405 LET I=I+1
410 IF I=32 THEN LET I=0
415 RETURN
A játékban a legfelső sorban vízszintesen futó "vadat" () kell középen eltalálni. Az alsó sorban levő "I" karaktert, a puskát, az M és Z billentyűkkel mozgathatjuk jobbra, ill. balra, az X gombbal lövünk. A játék 10 lövésből áll, az U gomb lenyomására újraindul.
A programban használt változók jelentése:
LOVEDEK - a hátralévő lövések száma; PONT - a találatok száma; X - a "puska" X koordinátája; A$, B$ - az animációhoz szükséges változók; I - a "vad" X koordinátája (jobbról).
A programban az 5-30. utasítások a kezdeti értékeket állítják be, a 40-60. utasítások a puskát mozgatják. Az X lenyomásakor a 70. utasítás elküldi a programot a lövést kirajzoló 100.-115. utasításokra. Ha a lövés talált, a találatot elkönyvelő 200-210. utasítások végrehajtása következik. A 125., ill. 215. utasitás ellenőrzi, nem fogyott-e el a lőszer. Ha igen, akkor a 300-330. utasítások kiírják az eredményt; a 325. utasítás az újraindítást végzi.
A vízszintes irányú mozgatást a "vadat" mozgató 400. kezdetű szubrutinon, és a játék végén az elért eredményt a játék újraindításáig fényújságszerűen mozgató 310-330. utasításokon figyelhetjük meg. A vízszintes mozgatás lényege az, hogy egy teljes sorhosszúságú szövegváltozót kettévágunk, és a két darabot a másodikkal kezdve egymás mellé írjuk. A vágás helyét változtatva (I változó) vízszintes irányú mozgás képét kapjuk.
A lövedék függőleges irányú mozgatását a 100-115. utasítások végzik. A felfelé mozgatás itt a SCROLL (105.) utasítással történik. Ennek hatására a legfelső sor ugyan eltűnik, de az azonnal következő 400-415. utasítások újra megjelenítik azt. Így nem okoz problémát, hogy a SCROLL az összes sort mozgatja. Ha a programban a képernyőnek csak egy részét akarjuk függőlegesen mozgatni, akkor jobb a PRINT AT utasítást használni. Ilyenkor ügyelni kellett arra, hogy az előző állapothoz tartozó kép nemkívánatos részeit töröljük a képernyőről.
A képernyő kezelését a POKE utasítás és a PEEK függvény segítségével is megoldhatjuk. Mint már említettük, a képernyőre kerülő információt a ZX81 a D-FILE memóriarekeszeiben tárolja. A képernyőre közvetlenül úgy írhatunk, hogy a POKE utasítással megváltoztatjuk a D-FILE memóriáinak tartalmát. A D-FILE közvetlenül a BASIC program után helyezkedik el, így a kezdőcíme a program hosszától függ. A D-FILE kezdő címét a 16396-97 memóriarekeszek tartalmazzák. A display tartomány szerkezetét már az első fejezetben tárgyaltuk. Ennek alapján a képernyő n. sorában az m. karakter kódját tároló memória címe a következő:
PEEK 16396+256*PEEK 16397+n*33+m+1
Ha tehát a képernyő n. sorának m. oszlopába egy "X" karaktert akarunk tenni, akkor a PRINT AT n,m;"X" helyett a következőt írhatjuk:
POKE(PEEK 16396+256*PEEK 16397+n+33+m+1),CODE X
Ugyanígy a
STR$ PEEK(PEEK 16396+256*PEEK 16397+N*33+M+1)
értéke az adott pozícióban lévő karakter lesz.
A D tartományba való beírásra, ill. kiolvasásra lássuk a következő példaprogramot.
Autóvezetés (16 K RAM szükséges)
10 LET PONT=0
20 LET A=7
30 CLS
40 DIM X(4)
50 DIM C(4)
60 LET X(1)=12
70 LET X(2)=X(1)-2
80 LET X(3)=X(2)-2
90 LET X(4)=X(3)-2
100 LET C(4)=4
110 LET D=PEEK 16396+256*PEEK 16397+i
120 RAND
130 LET A$=" "
140 LET B$=" "
150 LET D$=" "
160 LET C$=" "
170 LET A=A-(INKEY$="Q")+(INKEY$="P")
180 IF A<0 THEN LET A=0
190 IF A>18 THEN LET A=18
200 GOSUB 400
210 FOR I=1 TO 4
220 LET C(I)=X(I)-1
230 NEXT I
240 LET A$=" "
250 LET B$=" "
260 LET C$=" "
270 LET D$=" "
280 LET A=A-(INKEY$="Q")+(INKEY$="P")
290 IF A<0 THEN LET A=0
300 IF A>18 THEN LET A=18
310 GOSUB 400
320 LET X(2)=C(1)-1
330 LET X(3)=C(2)-1
340 LET X(4)=C(3)-1
350 LET X(1)=5*RND*SGN (RND-.5)
360 IF X(1)<7 THEN LET X(1)=7
370 IF X(1)>15 THEN LET X(1)=15
380 GOSUB 400
390 GOTO 130
405 PRINT AT 18,A;" "
410 PRINT AT 19,A;" "
420 PRINT AT 20,A;" "
430 PRINT AT 21,A;" "
440 POKE D+4*33+X(1),CODE A$
450 POKE D+5*33+C(1),CODE C$
460 POKE D+4*33+X(1)+7,CODE B$
470 POKE D+5*33+C(1)+9,CODE D$
480 POKE D+6*33+X(2),CODE A$
490 POKE D+8*33+C(2),CODE C$
500 POKE D+6*33+X(2)+11,CODE B$
510 POKE D+8*33+C(2)+13,CODE D$
520 POKE D+11*33+X(3),CODE A$
530 POKE D+14*33+C(3),CODE C$
540 POKE D+11*33+X(3)+15,CODE B$
550 POKE D+14*33+C(3)+17,CODE D$
560 POKE D+18*33+X(4),CODE A$
570 POKE D+18*33+X(4)+19,CODE B$
580 IF PEEK (D+21*33+C(4))=128 THEN STOP
590 IF PEEK (D+21*33+C(4)+21)=128 THEN STOP
600 POKE D+21*33+C(4),CODE C$
610 POKE D+21*33+C(4)+21,CODE D$
620 LET PONT=PONT+1
630 PRINT AT 0,0;PONT
640 RETURN
A játékban egy kanyargós úton kell vezetnünk autónkat. Az autó a "P" és "Q" billentyűkkel irányítható balra, ill. jobbra, az utat kilométerkövek jelzik. A programban használt változók jelentése:
PONT - a játékos pontjainak száma; A - az autó helyzetét jelző érték; X(4), C(4) - a kilométerkövek X koordinátái; D - a D-FILE kezdőcíme; A$, B$, C$, D$ - a kilométerkövek.
A program két ciklusból áll, ezek felváltva ismétlődnek. Az első ciklus az X(1)-X(4) pozícióknak megfelelő helyre kirajzolja az út bal szélét jelző kilométerköveket, majd a perspektívának megfelelő távolságra a jobb oldali köveket. Ugyanezen ciklusban törlődnek az előző képhez tartozó C(1)-C(4) értékeknek megfelelő helyeken lévő kilométerkövek. A másik ciklus a C(1)-C(4) értékeknek megfelelő helyekre rajzol, és az X(1)-X(4) helyeken töröl. A ciklusok
között az X(1)-X(4), ill. a C(1)-C(4) értékek megváltoznak (egyszerre csak az egyik), az új értékek kiszámítása az előző helyzetből történik.
A program 10-100. utasításai a változók kezdeti értékét állítják be. A 130-160. utasítások az első ciklusnak megfelelő A$, B$, C$, és D$ paramétereket állítják be. Mivel ebben a ciklusban az X(1)-X(4) helyekre rajzolunk kilométerköveket, az A$ és B$ értéke lesz az út két oldalát jelző karakter (, ). A C(1)-C(4) helyeken a korábbi kép törlődik, ezért C$ és D$ értéke " ". E változókra azért van szükség, mert a program ugyanazt a szubrutint használja mindkét ciklusban (400-640). A szubrutin az x vektorral kiszámított helyekre az A$ és B$, a C vektornak megfelelő helyekre pedig a C$ és D$ karaktereket rajzolja. Az A$-D$ változók értéke határozza meg, hogy ez kiírás ( és ), vagy törlés (" ").
A feltételek beállítása után 170. sor megvizsgálja, hogy a játékos változtat-e az autó helyzetén. A 180-190. utasítások nem engedik a képből való kifutást. A 200. utasítás a rajzolórészt futtatja le, a képernyőn megjelenik a paramétereknek megfelelő kép. A 210-230. utasítások az új kép paramétereinek kiszámítását kezdik meg: az előző kép X(I) értékeinek megfelelő C(I) értékeket számítja ki. Ezután az új ciklusnak megfelelő A$, B$, C$ és D$ értékek beállítása következik (mivel most az X(1-4) változóknak megfelelő helyeken törölni kell, A$ és B$ lesz " "). Ezután az első ciklussal azonos utasítások következnek. Az új kép kirajzolása után a következő kép paramétereinek kiszámítása következik. A képen feltűnő új útszakasz helyét a 350-370. sorok jelölik ki. A rajzolórész ismételt működtetése után a program visszatér az első ciklusra.
A rajzolóblokkban a 400-430. utasítások az autó orrát rajzolják ki a képernyőre. A 440-570. ill. 600.-610. sorok az előző képnek megfelelő pontokat törlik, az új pontokat pedig felrajzolják az ismertetett módszerrel. Az 580-590. utasítások vizsgálják meg, hogy azon a helyen, amelyre az utolsó kilométerkő kerül, nincs-e az autó orrához tartozd "" karakter. Ha igen, az karambolt jelent, ami egyben a játék vége is.
A rajzolóutasításokat természetesen lehetett volna ciklusba szervezni (ezzel memóriát takarítanánk meg), de ez a rajzolás amúgy is nem túl nagy gyorsaságát tovább rontaná.
A POKE utasítással a képernyő kezelése úgy is megvalósítható, hogy nem a D-FILE memóriáiba, hanem a képet meghatározó PRINT utasításokat tároló memóriacellákba avatkozunk be. Ez a módszer azonban hátrányos, mert lassabb, mintha közvetlenül a D-FILE-ba írnánk. Előnye viszont, hogy az új kép gyakorlatilag egyszerre (és nem részenként), a PRINT utasítás végrehajtásakor jelenik meg a képernyőn. Így, ha csak a képnek egy kis részét változtatjuk meg (pl. autóvezetés-program), akkor jobb a D-FILE memóriarekeszeire alkalmazni a POKE utasítást. Ha a kép nagyobb részét akarjuk megváltoztatni, vagy nem szeretnénk, ha az új kép folyamatosan épülne fel, jobb az utóbbi módszert követni. Legyen erre példa a következő program.
Megfigyelőképesség-teszt (8 K RAM szükséges)
10 LET P=0
20 LET A=INT (RND*26+38)
30 PRINT " E SORBAN 32 INVERZ SPACE VAN"
40 PRINT " 30 SPACE "
50 PRINT " 30 SPACE "
60 PRINT " 30 SPACE "
70 PRINT " 30 SPACE "
80 PRINT " 30 SPACE "
90 PRINT " 30 SPACE "
100 PRINT " E SORBAN 32 INVERZ SPACE VAN"
110 PAUSE 100
120 RAND
130 LET Y=INT (6*RND)
140 LET X=INT (30*RND)
150 CLS
160 IF PEEK (16601+40*Y+X) THEN GOTO 130
170 POKE 16601+40*Y+X,A
180 IF P=0 THEN GOTO 260
190 PRINT AT 10,0;"MI VOLT AZ UJ KARAKTER ?"
200 FOR I=1 TO 300
210 LET Q$=INKEY$
220 IF Q$<>"" THEN GOTO 240
230 NEXT I
240 IF CODE Q$<>B THEN GOTO 310
250 PRINT "JO VALASZ."
260 LET P=P+1
270 PAUSE 50
280 LET B=A
290 CLS
300 GOTO 20
310 PRINT "ROSSZ VALASZ."
320 PRINT
330 PRINT "A HELYES VALASZ: ";CHR$ B
340 PRINT
350 PRINT "PONTJAID SZAMA: ";P
360 PRINT
370 PRINT "A TESZTNEK VEGE."
380 FOR Y=0 TO 5
390 FOR X=0 TO 29
400 POKE 16601+40*Y+X,0
410 NEXT X
420 NEXT Y
430 PRINT "BARMELY BILLENTYURE UJRA INDUL."
440 PAUSE 4E4
450 CLS
460 RUN
A program hatására, a képernyőn egy keret jelenik meg, amiben egyre több és több betűt láthatunk. A kép csak két másodpercre jelenik meg, utána eltűnik. A teszt résztvevőjének meg kell mondania, hogy az előző állapothoz képest mivel bővült a kép. A betűk elhelyezkedése a keretben véletlenszerű. Rossz válasz esetén a teszt véget ér.
A programban használt változók a következők:
P - a jó válaszok száma; A, B - az új betű kódja; X, Y - az új karakter koordinátái; Q$ - INKEY$ karakter (a játékos tippje).
A program első két utasítása P és A értékét állítja be. A 30-100. utasítások a keretet rajzolják ki. Az utasításokat most nem szabad ciklusba szervezni, mivel a program további részei éppen ezeket a PRINT utasításokat változtatják meg. A 110. utasítás időt ad a játékosnak a kép megszemlélésére (a kép az első ciklusban még üres, de később egyre bonyolultabb). A 120. utasítás biztosítja, hogy a tesztet más alkalommal újra futtatva ne a korábbi betűsorozat ismétlését kapjuk. A 130-140. sorok a képernyőre kerülő új karakter helyét jelölik ki.
A BASIC program a 16509 című memóriarekesznél kezdődik. E programban azt szeretnénk, ha a képernyőt meghatározó 40-90. utasítások a program futása közben megváltoznának. Kiszámítható, hogy a 40. utasítás első betűköz karakterét a 16601-es memóriarekesz tárolja. Egy 32 karaktert kinyomtató PRINT utasítás (mint a 40., 50., 60., stb.) 40 memóriarekeszt foglal el. Így annak a rekesznek a címét, mely a PRINT utasításokban az Y. sor X. karakterét meghatározza a következő képlettel kaphatjuk meg:
16601+40*Y+X
Ezek alapján a 160. utasítás megvizsgálja, hogy a következő karakternek kijelölt hely nem foglalt-e, ha igen újabb hely kerül kijelölésre. A 170. utasítás a megfelelő memóriacímet megváltoztatja, így a program 40-90. sorának valamelyike módosul. Ezután a 190. utasítás kiírja a kérdést: mi volt az új karakter. A 200-230. utasítások időt hagynak a válaszra. Az idő mérése itt nem PAUSE utasítással történik, mert a billentyű megérintése továbbengedné a programot, és ha a billentyűt túl gyorsan engedik el, az INKEY$ függvényt tartalmazó utasítás esetleg nem kerül még végrehajtásra. A programban alkalmazott megoldásnál azonban az INKEY$ értelmezése, vagy az idő lejárta léptet ki a ciklusból. A 240. utasítás megvizsgálja, hogy a kapott válasz helyes-e. Az összehasonlítás a B változóval történik, mivel az A változó már a következő ciklusban kiírandó karakter kódját tartalmazza. Ha a válasz nem jó, a játék végét alkotó 310-480. utasítások végrehajtása következik. Ha a válasz helyes, a 260. utasítás megnöveli a pontok számát, a 280. utasítás hatására a helyes választ tartalmazó B változó felveszi a következő értéket. Ezután a program újrafuttatása következik (csak a kezdeti pontszámot beállító utasítás marad ki), de a megváltoztatott PRINT utasítások miatt most már az új kép kerül a képernyőre.
E megoldás alkalmazásánál nehéz feladat a PRINT utasítás megfelelő memóriacímét kiszámítani, egyszerűbb megkeresni azt. Az EDIT utasítás segítségével rakjunk a keresett pozícióba egy olyan karaktert, amit a programban más helyen nem használunk (pl."£"). Ezután a programunk végére rakjuk a következő utasításokat:
9990 LET I=16509
9991 IF PEEK I=CODE"£" THEN GOTO 9995
9992 LET I=1+1
9993 GOTO 9991
9995 PRINT I
9996 STOP
Az utasítások (GOTO 9990-nel) lefuttatása után a ZX81 kiírja a keresett memória számát. Ezután a segédutasítások törölhetők, a PRINT utasításba pedig visszaírható az eredeti karakter. Vigyázni kell azonban arra, hogy a megtalált memóriacím változik, ha a programot - pl. utasítás beszúrásával - úgy módosítjuk, hogy az adott PRINT utasítás előtt is történik változás.
III./3. POKE és PEEK alkalmazásai
A PEEK függvény (ellentétben a POKE utasítással) a ZX81 ROM memóriaterületeire is alkalmazható. Így azok a megváltoztathatatlan információk, amelyek a gép
működését szabályozzák, BASIC programjaink számára is elérhetők és felhasználhatók. A ROM területeken találhatók pl. az egyes karakterek alakjai, és hogy egy utasítás leírásakor mi jelenjen meg a képernyőn (tehát, pl. hogy a GOTO utasítás a G+O+T+O betűkből áll).
A karakterek alakja a 7680 és a 8191 memóriacímek között található. Egy karakter a képernyőn 8x8 pontból áll, egy-egy pont lehet fekete, vagy fehér. Ez azt jelenti, hogy 2^64-féle karakter képzelhető el (64 pontot ennyiféleképpen tudunk két színnel kiszínezni). A karakter előállításához szükséges információnak a 2^64 lehetőséget meg kell tudnia különböztetni, ehhez minimálisan 64 bit, azaz 8 byte szükséges. A 7680 címtől kezdve az első nyolc byte a 0 kódszámú betűköz, a második nyolc byte az 1 kódszámú grafikus szimbólum, az n. nyolc memóriarekesz pedig az n-1. kódszámú karakter alakját írja le. A 63 kódszámú "Z" betű az utolsó karakter, amelynek alakja itt tárolódik. A többi karakter előállítható vagy ezek invertálásával (a fekete és a fehér pontok felcserélésével), vagy több karakter egymás mellé rakásával (utasítások, függvénynevek).
Egy adott karakter alakját tároló nyolc memóriarekesz a következőképpen határozza meg az alakot: ha a nyolc rekesz tartalmát bináris alakban egymás alá írjuk, az "1" számjegyek a fekete, a "0" számjegyek pedig a fehér pontok helyét jelölik ki, mint ahogy ez a 6. ábrán is látható.
karakter |
a sornak megfelelő cím |
a memóriában lévő érték |
az érték bináris alakja |
7688 |
240 |
11110000 |
|
7689 |
240 |
11110000 |
|
7690 |
240 |
11110000 |
|
7691 |
240 |
11110000 |
|
7692 |
0 |
00000000 |
|
7693 |
0 |
00000000 |
|
7694 |
0 |
00000000 |
|
7695 |
0 |
00000000 |
6. ábra: Az "1" kódszámú grafikus karakter alakjának tárolása
A következő program szövegek kinagyítására alkalmas.
Szövegnagyító program (8 K RAM szükséges)
10 CLS
20 PRINT "Y IRANYU NAGYITAS(4-8-12...):";
30 INPUT YN
40 PRINT YN
50 LET YN= INT (YN/4)
60 PRINT "X IRANYU NAGYITAS(4-B-12...):";
70 INPUT XN
80 PRINT XN
90 LET XN= INT (XN/4)
100 PRINT "SZOVEG HELYE (Y=0-40):";
110 INPUT Y0
120 PRINT Y0
130 PRINT "SZOVEG HELYE (X=0-40):";
140 INPUT X0
150 PRINT X0
160 PRINT "A SZOVEG: ";
170 INPUT A$
180 IF LEN A$*XN*4+X0>64 OR YN*8+Y0>44 THEN GOTO 10
190 CLS
200 FAST
210 FOR I=1 TO LEN A$
220 LET A= CODE A$(I)
230 FOR J= 0 TO 7
240 LET B= PEEK (7680+A*8+J)
250 FOR K=1 TO 8
260 LET C=0
270 IF B-2*INT (B/2) THEN LET C=1
280 LET B= INT (B/2)
290 IF C=0 THEN GOTO 350
300 FOR L=1 TO XN
310 FOR M=1 TO YN
320 PLOT X0+B*XN*(I-1)+(7-K)*XN+L,Y0+(7-J)*YN+M
330 NEXT M
340 NEXT L
350 NEXT K
360 NEXT J
370 NEXT I
380 SLOW
390 STOP
A programban szereplő változók jelentése a következő:
XN, YN - a kiírandó szöveg megfelelő irányú nagyítása; mivel a program csak négy egész számú többszörösére tud nagyítani, ezért néggyel osztva kerül tárolásra; X0, Y0 - a kiírandó szöveg bal alsó sarkának koordinátái, a PLOT utasítás szerinti X,Y koordinátáknak megfelelően; A$ - a kiírandó szöveg; A - az éppen kiírás alatt álló betű kódja; B - a kiírás alatt álló betű képének egy sorát tároló memória; a bináris számmá való átalakítás során B új értéket vesz fel; C - azt jelzi, hogy az éppen vizsgált pont fekete vagy fehér; I - ciklusváltozó; azt jelzi, hogy a szöveg hányadik betűjénél tart a kiírás; J - ciklusváltozó; azt jelzi, hogy az adott betű hányadik sorsnak kíírása folyik éppen; K - ciklusváltozó; azt jelzi, hogy az adott sor hányadik pontjának kiírása történik; M, N - ciklusváltozók; azt biztosítják, hogy a nagyításnak megfelelő számú pont jelenjen meg a képernyőn.
A program kezdeti utasításai (10-170.) a kiíráshoz szükséges adatokat veszik be. A 180. utasítás megvizsgálja, hogy a kapott adatok alapjen a kép ráfér-e a képernyőre (ha nem, újra kezdődik az adatok bekérése). A 200. utasítás, mivel a kiírás viszonylag lassú, FAST üzemmódba kapcsolja át a ZX81-et. A 210. utasításban kezdődő ciklus a kiírandó szöveget betűkre bontja, mert a kiírás betűnként történik. Mivel az egyes karakterek alakját tartalmazó információ a karakterek kódja szerinti sorrendben találhatók meg, az n kódszámú karakter alakját a 7680+8*n címtől kezdődő nyolc memóriarekesz adja meg. A 230-250. utasítások sorra veszik a karakter alakját tároló rekeszek tartalmát. Az ezt követő ciklus (250. utasítással kezdődően) az adott érték bináris alakra alakítását végzi el: ha a szám páros, a bináris érték utolsó szemjegye "0", ha páratlan, "1". Ezután a szám felét véve (a maradék elhagyásával) ismét meg kell vizsgálni a paritást; ezt nyolcszor elvégezve a szám bináris alakját kapjuk. Minden egyes bináris számjegy előállítása után a program megvizsgálja, hogy az érték különbözik-e nullától, azaz az adott pont fekete-e (290. utasítás). Ha igen, a 300-340. utasítások beszínezik a nagyított képben a pontnak megfelelő területet: az X irányú nagyításnak megfelelő számú pontot raknak egymás mellé, ill. az Y irányú nagyításnak megfelelőt egymás fölé.
A szövegnagyító program elsősorban más programok kiegészítéseként használható. Ekkor célszerű elhagyni a 10-180. utasításokat, a szükséges értékek megadását a fő programban kell biztosítani. A többi utasítást is természetesen át kell számozni annak megfelelően, hagy a szövegnagyító szubrutint hol helyezzük el a programban. Mivel feltehetően a szövegnagyító többször kerül felhasználásra a programban, a STOP utasítást RETURN-ra kell cserélni.
A program ebben a formában nem alkalmas inverz karakterek kiírására. Inverz karakter kiírásakor a 260. utasítást LET C=1-re, a következő utasításba pedig a ...THEN LET C=1 részt THEN LET C=0-ra kell változtatni.
A ROM tartalmának felhasználására mutatott példa után vizsgáljuk meg, melyek azok a RAM-hoz tartozó memóriarekeszek, melyeket érdemes POKE utasítással megváltoztatni vagy PEEK függvénnyel kiolvasni. A ZX81 programozásakor lehetőségünk van a rendszerváltozók megváltoztatására, vagy kiolvasására. A legfontosabb hasznosan felhasználható rendszerváltozók a következők: RAMTOP; MODE; D-FILE; DF-CC; FRAMES. Vegyük sorra, hogyan használhatók a BASIC programban!
a) RAMTOP
A RAMTOP rendszerváltozó a 16388 / 16389 címen található, tehát értéke PEEK 16388 + 256*PEEK 16389. Ha a RAMTOP értékét megváltoztatjuk, a RAMTOP-nál nagyobb című memóriák "védett területté" válnak: tartalmuk még NEW utasítás hatására sem törlődik. E változó POKE utasítással való megváltoztatásával elérhető, hogy egy újabb program beolvasásakor az előző programban kiszámított értékek ne vesszenek el, és azzal tovább számolhatunk. Az így előállított terület a gépi kódú programok tárolására is optimális.b) MODE
A MODE változó (16390) azt jelzi, hogy a ZX81 K, L, F, vagy G kurzor-módban van. A különböző módoknak megfelelő értékek:
K és L: 0;
F: 1;
G: 4.
A MODE rendszerváltozó POKE utasítással való megváltoztatása elsősorban akkor hasznos, ha az INPUT utasítással bekért adatot szeretnénk befolyásolni. Ha pl. azt szeretnénk, hogy az INPUT utasítással beolvasott szöveg inverz alakú legyen, az INPUT elé POKE 16390,4 utasítást kell írni.c) D-FILE
A D-FILE változó (16396-16397) a D-tartomány kezdőcímét mutatja. Ha a képernyőre közvetlenül POKE utasítással akarunk írni, akkor a megfelelő memóriarekesz megtalálásához a D-FILE értékének kiolvasása szükséges. Mivel a D-tartomány közvetlenül a BASIC programot tároló rész után található, a tartomány kezdőcíméből meghatározható a BASIC program hossza. A program által elfoglalt területet a
PEEK 16396 + PEEK 16397*256 - 16509
kifejezés adja.
Felmerülhet, hogy a D-FILE rendszerváltozó POKE utasítással való megváltoztatásával a képernyőt egy, pillanat alatt átírhatjuk. Ez nincs teljesen így. Ha pl. a RAMTOP feletti területen előállítunk egy memóriablokkot, melyet a D-tartománynak megfelelően szervezünk (NEW LINE + 32 karakter + NEW LINE + ...), és a D-FILE változót a blokk első memóriacímére állítjuk be, a következő történik: a képernyőn megjelenik ugyan az általunk előkészített kép, de a ZX81 irányítását elveszítjük (a többi rendszerváltozó nincs összhangban a D-FILE értékével). A gép újraindítása ilyen esetben csak a hálózatból való kihozás után lehetséges. Hasonlóan járhatunk, ha pl. a RAMTOP megváltoztatásakor túl alacsony értéket adunk meg.d) DF-CC
A DF-CC rendszerváltozó (16398-16399) a D-tartomány egyik memóriacímét határozza meg. Az itt kijelölt memóriarekesz lesz az, aminek tartalma a következő PRINT utasítás végrehajtása során megváltozik. Pl. a PRINT AT 4,0;"X"; utasítás végrehajtása után a DF-CC rendszerváltozó a 4. sor 1. pozícióját tároló rekesz címét adja; ha a PRINT utasítás nem ";" karakterrel zárult volna, akkor az 5. sor 0. pozíciója lenne a kijelölt. A DF-CC rendszerváltozó POKE-kal való megváltoztatásával a PRINT utasítások vezérelhetők, a PEEK-kel való kiolvasásával pedig a legutóbbi kiírás helyére következtethetünk.e) FRAMES
A FRAMES rendszerváltozó (16436-16437) időmérésre szolgál. A ZX81 bekapcsolásakor a változó 15. bitjének értéke 1, a többi bit 0. Ezután a változó értéke a tv-képernyőjére küldött jeleknek megfelelően másodpercenként 50 alkalommal eggyel csökken. Ha a változót idő mérésére használjuk, figyelembe kell venni, hogy a PAUSE utasítás megváltoztatja a változó értékét. A PAUSE hatására a 15. bit értéke 0 lesz, a 0-14. bit pedig az utasításban megadott értékre állítódik be. Így a FRAMES változó értékének nullává válása jelzi a szünet végét. Ha a PAUSE utasítás végrehajtása közben a ZX81 valamelyik billentyűjét megérintik, a 15. bit ismét egyre változik, és a program futása folytatódik.
A FRAMES rendszerváltozó használatát mutatják be a következő programok is.
Reakcióidő-mérés
10 LET ATL=0
20 RAND
30 FOR I=1 TO 10
40 PAUSE 50
50 CLS
60 IF RND<0.99 THEN GOTO 60
70 IF INKEY$<>"" THEN GOTO 60
80 POKE 16437,255
90 POKE 16436,255
100 PRINT AT 10,12;"MOST"
110 IF INKEY$="" THEN GOTO 110
120 LET IDO=(65530-PEEK 16436 -256*PEEK 16437)/50
130 PRINT "REKCIO IDO: ";IDO;" MASODPERC"
140 PRINT I+1;". MERES KOVETKEZIK"
150 LET ATL=ATL+IDO
160 NEXT I
170 CLS
180 PRINT "ÁTLAGOS REAKCIOIDO: ";ATL/10;" SEC"
190 STOP
A program egy tíz mérésből álló reakcióidő-tesztet hajt végre. A program indítása után bizonyos - véletlenszerű - idő eltelte után a képernyőn megjelenik a MOST felirat. Innen kezdve a ZX81 méri az időt, ami bármelyik billentyűjének megérintéséig eltelik.
A 10-20. utasítások a kezdeti feltételeket állítják be. A 30. utasítás a tíz méréshez szükséges ciklust nyitja meg. A 60. utasítás a véletlenszerű szünetet biztosítja. A 70. utasítás megvizsgálja, nem nyomták-e le valamelyik billentyűt előre (ami megtörténhet akkor is, ha a szünet túl rövid, és a billentyűt még az előző menetből nem engedték el). A 80-90. utasítások a FRAMES változót állítják be maximális értekre (255+255*256=65535). A kis helyértékű byte beállítása azért történik később, mert ezalatt a másik byte értéke nem változik, míg fordított esetben a nagyobb értékű byte beállítása alatt a másik byte már mérné az időt. A 100. utasítás megadja a játékosnak a jelet, ezután a 110. utasítás végrehajtása ismétlődik addig, míg valamelyik billentyűt meg nem érintik. A 120. utasítás kiszámolja az eltelt időt, vagyis azt, hogy a FRAMES értéke mennyit csökkent. A valódi csökkenésből itt 5/50 másodpercet levonunk, ez közelítőleg az indítást jelző PRINT utasítás végrehajtásához szükséges id. A 130. utasítás kiírja a részeredményt, majd a ciklus végrehajtása után 180. utasítás a végeredményt.
A POKE utasítás és a PEEK függvény újabb alkalmazását mutatja be a következő példa. Az itt ismertetett program a kusza számozású programok átszámozására alkalmas. Természetesen a program a GOTO és GOSUB utasításokban szereplő sorszámokat is átszámozza az új sorszámoknak megfelelően, amihez a BASIC utasításokat tároló memóriaterületbe avatkozunk be. A kifejezéseket tartalmazó GOTO és GOSUB utasítások - pl. GOTO A - nem számozhatók át, így ezeket kerülni kell.
Átszámozó program (8K RAM szükséges)
9000 LET I=16509
9005 LET J=0
9010 DIM A(100)
9015 DIM B(100)
9020 PRINT "ATSZAMOZAS UTANI LEGKISEBB","UTASITASSZAM ?"
9025 INPUT K
9030 PRINT "LEPESKOZ ?"
9035 INPUT L
9040 FAST
9045 GOTO 9200
9050 LET I=16509
9055 LET J=1
9060 LET A=A(J)
9065 LET B=B(J)
9075 IF B=0 THEN LET B=9999
9080 IF PEEK (I+J)+256*PEEK I>=B THEN GOTO 9130
9085 POKE I,INT(K/256)
9090 POKE I+1,K-256*INT(K/256)
9095 LET K=K+L
9100 LET I=I+1
9110 IF 256*PEEK(I)+PEEK(I+1)=9000 THEN SLOW
9115 IF 256*PEEK(I)+PEEK(I+1)=9000 THEN STOP
9120 IF PEEK(I-1)=118 AND PEEK(I-3)<>118 THEN GOTO 9075
9125 GOTO 9100
9130 LET C1= INT K/1000
9135 LET C2= INT ((K-C1*1000)/100)
9140 LET C3= INT ((K-C1*1000-C2*100)/10)
9145 LET C4= K-C1*1000-C2*100-C3*10
9150 POKE A,C1+28
9155 POKE A+1,C2+28
9160 POKE A+2,C3+28
9165 POKE A+3,C4+28
9170 LET J=J+1
9175 GOTO 9060
9200 LET J=J+1
9205 LET I=1+1
9210 IF PEEK I=236 OR PEEK I=237 AND PEEK(I-2)<>118 THEN GOTO 9230
9215 GOTO 9205
9230 IF PEEK(I+1)=37 THEN GOTO 9050
9235 LET A=I+1
9240 LET B=1000*(PEEK(I+1)-28)+100*(PEEK(I+2)-28)+10*(PEEK(I+3)-28)+PEEK(I+4)-28
9245 LET M=1
9250 IF B<B(M) THEN GOTO 9300
9255 IF B(M)=0 THEN GOTO 9270
9260 LET M=M+1
9265 GOTO 9250
9270 LET A(J)=A
9275 LET B(J)=B
9280 GOTO 9200
9300 FOR N=J TO M+1 STEP -1
9305 LET A(N)=A(N-1)
9310 LET B(N)=B(N-1)
9315 NEXT N
9320 LET A(M)=A
9325 LET B(M)=B
9330 GOTO 9200
A program használatánál a következőkre kell ügyelni:
A programban szereplő változók a következők:
I - a vizsgált memória címe; J - azt jelzi, hogy a program a már megvizsgált részben hány GOTO és GOSUB utasítást tartalmazott; A(100), B(100) - a GOTO és GOSUB utasításokra vonatkozó adatok; B(I) jelenti azt a sorszámot, ahova az 1. GOTO v. GOSUB utasítás küldi a programot, A(I) pedig azt a memóriacímet, ahol a GOTO, ill. GOSUB utasítás tárolva van; A - A(I) aktuális értéke; B - B(I) aktuális értéke; K - az átszámozás utáni legkisebb sorszám; L - az átszámozás utáni lépésköz.
A program működése a következő:
A program először egyesével megvizsgálja a BASIC programot tartalmazó memóriákat. Ekkor "összeírja" a GOTO és GOSUB utasításokat (ill. azokat az IF
utasításokat, amelyekben GOTO és GOSUB szerepel). E szakaszban a 9000-9040. utasítások a kezdeti feltételeket állítják be, ezután az összeírást végző 9200-9330. utasítások végrehajtása következik. A 9210. utasítás azt vizsgálja, hogy az adott memóriacella nem GOTO vagy GOSUB utasítás kódját tartalmazza-e. A PEEK(I-2)<>118 feltétel azért szükséges, mert az utasítások sorszámát tároló memóriákban is előfordulhat a GOTO vagy a GOSUB kódja - természetesen sorszámként. Ha ott fordul elő, akkor két memóriarekesszel előbb a NEWLINE kódjának kell lennie (118). Így a PEEK(I-2)<>118 feltétel kiszűri azokat a memóriákat, amelyek ugyan 236 vagy 237 számot tárolnak, de nem GOTO, ill. GOSUB utasítást jelentenek. Ha a memória nem GOTO vagy GOSUB utasítást jelöl, a következő memória vizsgálata következik. Ellenkező esetben a 9230-9330. utasítások hajtódnak végre. A 9230. utasítás azt vizsgálja, hogy a GOTO utasítás után nem a "9" számjegy következik-e. Ha igen, akkor az utasítás (lévén itt csak négyjegyű szám állhat) egy 9000-es sorszámú utasításra küldi a programot, tehát már az újraszámozó program első GOTO utasítását találtuk meg. Ekkor a program befejezi a GOTO és GOSUB utasítások további keresését. Ha nem GOTO 9... utasításról van szó, akkor a megtalált helyet tárolja. Az A változó felveszi a következő memóriacímet (9235. sor), a B változó pedig azt a sorszámot, ahova a megtalált GOTO vagy GOSUB utasítás küldi a programot (9240. sor). Ezután az A és B értékek az A(I) és B(I) vektorok elemei lesznek, a vektorelemek a számokat B(I) nagyság szerinti sorrendjében tartalmazzák. A 9245-9265. utasítások
végigszaladnak a korábban beirt B(I) értékeken, és megkeresik azt az M sorszámot, ahova az új B érték illeszkedik. Ha az új szám a legnagyobb, akkor a sor végére kerül (9270-9275. utasítások). Ha az új szám nem a legnagyobb, akkor a 9300-9315. utasítások a további vektorelemeket eltolják, és a felszabaduló helyre a 9320-9325. utasítások beírják az új számpárt. Ezután ismét a 9200. utasításra tér vissza a program, azaz folytatódik a GOTO és GOSUB utasítások keresése.
Ha az összes GOTO és GOSUB megvan, sor kerülhet az utasítások átszámozására (9050-9175. utasítások). Ehhez meg kell keresnünk azokat a memóriacímeket, amelyek sorszámot tartalmaznak. A BASIC programot tartalmazó memóriacímek közül az első kettő biztosan utasítássorszámot jelöl. A 9100. utasítás megvizsgálja nem történt-e "hivatkozás" erre a sorszámra. Természetesen elég a B(I)-ben tárolt utasítás-sorszámok közül az elsőt megnézni, mivel az utasítások sorszáma is, és a B(I) értékek is nagyságrendi sorrenden vannak. Ha a programban szerepel e sorszámra való hivatkozás, akkor a megfelelő memóriarekeszekbe az új sorszámnak megfelelő érték kerül (9130-9175. utasítások), majd az A és B változók felveszik a következő értékeket. Ez természetesen lehet az előző sorszámmal azonos is, hiszen több helyről is küldhettük ugyanoda a programot. Ezért a végrehajtás a 9075. számú utasításnál folytatódik, így ha az új érték azonos a régivel, akkor itt is megtörténik a korrekció. Ha már nincs szükség több korrekcióra, sorra kerül az utasítás átszámozása. Természetesen a program előbb megvizsgálja, hogy nem végzett-e már, mert ha az átszámozandó sorszám 9000, akkor már az átszámozó program utasítása következne, amit már nem kell átírni. Az újabb átírandó sorszám megtalálását a 9120. sor végzi, hiszen a NEWLINE kódja (118) új sor kezdetét jelzi. Az utasítás azt is megvizsgálja, hogy a talált 118-as érték nem sorszám-e véletlenül.
A terület összes memóriacelláján végighaladva az utasítások a kívánt módon átszámozódnak, és a GOTO, ill. GOSUB szavak mögé is az új sorszámoknak megfelelő hivatkozás kerül.
III./4. Hang
A ZX81 nem rendelkezik olyan utasítással, amely közvetlenül hang előállítására alkalmas. Ennek ellenére előállíthatunk hangot, bár erre elsősorban gépi kódú programozással nyílik lehetőség. A ZX81 azonban lehetővé teszi azt is, hagy BASIC utasításokkal megszólaltassuk. Futtassuk le a következő programot:
10 SLOW
20 FAST
30 GOTO 10
A program hatására a televízió egy zümmögő hangot hallat, a képernyőn csíkokat láthatunk. A hang magasságát a SLOW és FAST utasítások közé helyezett CONT, REM, RAND vagy egyéb utasításokkal tudjuk szabályozni.
Az így előállított hangok arra alkalmasak, hogy a ZX81 jelezze, ha egy hosszabb számítással végzett (a példaként felhozott három utasítást a STOP utasítás helyére rakva). A BASIC hangalkotás előnye, - ellentétben a gépi kóddal előállított hangokkal, hogy, az amúgy is csatolt televízió hangszóróján keresztül hallható. Ez azonban csak hangeffektus, nincs lehetőség kép és hang egyidejű megjelenítésére.
III/5. Memóriatakarékos programozás
A memóriatakarékos programozási módszerek különösen akkor fontosak, ha nem áll rendelkezésünkre memóriabővítés, tehát 1 kilobyte szabad memóriaterület felett rendelkezünk csak. Ekkor minden megtakarított byte-nak nagy jelentősége lehet.
Természetesnek tűnik, mégis célszerű összeszedni a következőket:
A ZX81 egy szám tárolására 6 byte memóriát használ fel. Így célszerű minél kevesebb konstanst használni a programban. A következő megoldások azonos hatásúak, mégis különböző a memóriaigényük:
szokásos | memóriatakarékos | még jobb |
10 LET A=1 |
10 LET A=1 20 LET B=A |
10 LET A=SGN PI 20 LET B=A |
Egyéb memóriatakarékos helyettesítések:
LET A=0 helyett LET A= NOT PI LET A=4 helyett LET A=VAL "4"
A program által elfoglalt memóriaterületet tovább csökkenthetjük, ha a logikai relációkat használunk. Pl.:
10 IF INKEY$="5" THEN LET X=X-1
20 IF INKEY$="8" THEN LET X=X+1
helyett írjuk ezt:
10 LET X=X+(INKEY$="8")-(INKEY$="5")
Egy másik példa:
10 IF Q<>0 THEN GOTO 100
helyett írjuk ezt:
10 IF Q THEN GOTO 100
Természetesen még számos módszert találhatunk a felhasznált memóriaterület csökkentésére. A különböző módszerek memóriaigényét e fejezet 3. pontjában leírt módszerrel hasonlíthatjuk össze (D-FILE rendszerváltozó).
III./6. Egyéb BASIC utasítások helyettesítése
A ZX81 függvényei, utasításai közül több olyan hiányzik, ami a BASIC nyelv más változataiban megtalálható. Ez akkor okozhat problémát, ha egy más gépre készült programot akarunk a ZX81-be beírni.
A READ, RESTORE és DATA utasítások majdnem minden BASIC változatban megtalálhatók. Pl. a
10 READ A,B,C
20 DATA 5,6,7
utasítások hatására a megadott változók sorra felveszik a DATA utasításban megadott értékeket (tehát A=5, B=6, C=7 lesz). A RESTORE utasítás hatása a számítógép ismét a DATA lista elejére ugrik. Mivel a ZX81 nem rendelkezik ezekkel az utasításokkal, az adatokat többnyire LET utasítással kell bevinni. Az adatok tárolására REM utasításokat is alkalmazhatunk. Tegyük fel például, hogy a 11, 12, 13, 14, 15 számokat egy utasítással szeretnénk elraktározni. Kezdjük a programot az 1 REM "£$:? utasítással. Ennek hatására a BASIC terület megfelelő memóriarekeszei sorra felveszik a megadott karakterek kódjait. Mivel a " karakter kódja 11, és a többi karaktert is úgy választottuk, hogy kódjuk a tárolandó szám legyen, PEEK 16514 értéke 11, PEEK 16515 értéke 12 ... és a PEEK 16518 értéke 15 lesz. Ezzel a módszerrel egy utasítással szinte tetszőleges számú, 1 és 255 közé eső egész számot raktározhatunk el.
Nem találjuk a ZX81 utasításkészletében a DEF utasítást sem, ami a függvények definiálására alkalmas. A
DEF FNC(X,Y)=5*X+SIN(Y)
hatására a programban található FNC(A,B) kifejezések felveszik a DEF utasításban megadott képlettel kiszámított értéket. A ZX81 programozásakor a függvények képletét egy szubrutinban kell elhelyezni, és a függvény valamennyi előfordulása előtt a szubrutin hívásával ki kell számíttatni a megfelelő értéket. Az említett példánál tehát a program végére helyezzük el
9000 LET FNC=5*X+SIN Y
9005 RETURN
utasításokat, és a függvény minden előfordulása előtt a programlista tartalmazzon egy GOSUB 9000 utasítást. Ez a módszer az összetett függvények definiálását is lehetővé teszi, amelyre egyes BASIC változatokban az FNEND utasítás ad lehetőséget (összetett függvény pl.: FNA(x)=SIN X ha X>5, ellenkező esetben FNA(X)=0 ).
Elsősorban a régebbi BASIC változatokban található meg az ON utasítás. Az
ON A THEN 10,20,30, ill. az
ON A GOSUB 10,20,30
utasítás hatására, ha A=1, akkor a 10., ha A=2, akkor a 20, ha A=3, akkor a harmadikként megadott 30. sorszámú utasítás végrehajtása következik. Ez a ZX81-en is egyszerűen megoldható, írjunk
GOTO (10 AND A=1)+(20 AND A=2)+ (30 AND A=3)
utasítást! Példánkban az egyszerűbb GOTO 10*A utasítás is azonos hatású, de ilyen egyszerűsítés nem mindig kínálkozik. GOTO helyett adott esetben természetesen GOSUB-ot kell írni.
A program lépésenként való végrehajtására (EXECUTE utasítás) szintén nincs lehetőségünk. Ha csak egy utasítást akarunk végrehajtatni, akkor két módszert követhetünk:
A ZX81 szövegváltozókat kezelő módszerei nem teszik szükségessé a LEFT$, RIGHT$, MID$ és TL$ függvények használatát, amelyek sok BASIC változatban előfordulnak. A megfelelő kifejezések a ZX81-en:
más változat ZX81 LEFT$(A$,X)
RIGHT$(A$,X)
MID$(A$,Y,X)
SUB$(A$,Y,X)
SEG$(A$Y,X)
TL$(A$)A$( TO X)
A$(LEN A$-X+1 TO )
A$(Y TO Y+X-1)
A$(Y TO Y+X-1)
A$(Y TO X)
A$(2 TO)
Ritkán fordulnak elő az AFT(A$,B$,N), BEF(A$,B$,N), DEL(A$,B$,N), INS(A$,B$,N) MUL$(A$,N), PER$(A$,B$,C$,N), SDL$(A$,B$,N) függvények. Ezek helyettesítése szubrutinokkal történhet.
IV. A mikroprocesszor programozása
IV./1. A gépi kód
A könyv első három fejezete képet adott arról, hogy milyen feladatokat oldhatunk meg BASIC nyelven. Mielőtt áttérnénk a gépi kódú programozásra, foglaljuk össze a BASIC programozás határait, vagyis azokat az okokat, ami miatt a hivatásos software-fejlesztőkön kívül másnak is érdemes az ASSEMBER szintű programozással foglalkozni.
A BASIC programok legnagyobb hibája, hagy a futás viszonylag lassú. Ez különösen akkor zavaró, ha igen sok műveletet végeztetünk a számítógéppel, vagy egy játékprogramban gyors váltásokra van szükség (pl: Autóvezetés). A gépi kódban írt programok akár tízszeres sebességgel is futhatnak a megfelelő BASIC programhoz képest. Hogy miért? Ennek oka főleg az, hogy a BASIC utasítások univerzálisak, így az adott programhoz fölösleges műveleteket is végeztetnek a számítógéppel, ez pedig időt vesz igénybe. A gépi kódú program viszont közvetlenül a mikroprocesszort vezérli, így csak az adott feladat szempontjából szükséges lépéseket végeztetjük el. Pl. BASIC-ben az INKEY$ függvény alkalmazásakor az összes billentyű megvizsgálásra kerül, gépi kódban viszont elég csak a szóba jövő billentyűket figyelni.
Látni fogjuk, hogy az ASSEMBLER programozás technikája alapvetően nem tér el a BASIC-től. A fő különbség annyi, hogy az ASSEMBLER utasítások elemi lépéseket tartalmaznak. Ha a programozást egy ház építéséhez hasonlítjuk, a BASIC a paneles, az ASSEMBLER pedig a téglás építkezés megfelelője. Könnyebb és gyorsabb a programot kész egységekből összerakni, de a fáradságosabb, aprólékosabb munkával készülő ASSEMBLER PROGRAM lehetővé teszi a finomabb részletek kidolgozását is, így a program jobban idomul a problémához. Így pl. lehetőség nyílik a számítógép ki és bemeneteinek közvetlen vezérlésére is. Módunk van pl. zenei hangok előállítására a "MIC" kimeneten vagy akár ilyen jelek bemenő adatként való értelmezésére. Ugyancsak mód nyílik arra, hogy a programok rögzítését és beolvasását meggyorsítsuk, ehhez a "SAVE"
jelek előállítását és analizálását végző programot kell újraírni.
Ha a gépi kódú programozást csak a futásidő lerövidítésére akarjuk használni, akkor jó szolgálatot tesznek az un. "COMPILER" programok. Ezek BASIC programjainkat "lefordítják" ASSEMBLER szintre, azaz olyan gépi kódú programot készítenek, ami azonos hatású az adott BASIC programmal. Mivel univerzális COMPILER még nem készült, a rendelkezésre álló BASIC lehetőségek szűkebbek lesznek. A tiltott utasítások, függvények a különböző COMPILER programoknál mások-mások.
Ha azonban az ASSEMBLER egyéb lehetőségeit is ki akarjuk használni (vagy nem sikerült COMPILER-hez jutnunk), magunknak kell gép kódú programunkat elkészíteni. Fogjunk hozzá!
IV./2. A Z-80 (Z-80A) mikroprocesszor
Ahhoz, hogy gépi kódú programokat készíthessünk először meg kell ismerkednünk a mikroprocesszorral. Mint már említettük, a mikroprocesszor a számítógép lelke itt kerülnek végrehajtásra a kijelölt műveletek. Hogyan történik ez? Először meg kell adnunk, hogy milyen művelet elvégzését kérjük, és milyen adatokon. A mikroprocesszor csak számokat ismer, így a művelet megadása egy kódszámmal történik. Ezután a mikroprocesszor elvégzi a kívánt műveletet, és megadja az eredményt. Ez a kapcsolattartás a mikroprocesszorban levő memóriarekeszeken, az ún. regisztereken keresztül történik. Ezekbe beírva vihetünk be adatot, ill. innen olvashatjuk ki az eredményt. A mikroprocesszor csak néhány művelettípus elvégzésére képes.
A leggyakrabban előforduló művelet az, hogy egy regiszter bitjeinek tartalmát átmásoltatjuk egy másik regiszterbe. Az egyik regiszter szerepét átveheti egy külső memóriacella is.
Az utasításkészlet másik csoportja az aritmetikai utasításokat tartalmazza. Itt csak összeadásra és kivonásra van lehetőségünk. Ha bármi más műveletet akarunk végeztetni a mikroprocesszorral, akkor ehhez megfelelő gépi kódú program szükséges. Ilyen programok találhatóak a ZX81 ROM-jában is, ezért tud a gép szorozni, hatványozni vagy függvényértékeket kiszámítani.
A harmadik utasításcsoport a logikai utasításokat tartalmazza. A mikroprocesszor háromféle logikai műveletet ismer. Minden logikai műveletnél az eredmény kiszámítása bitenként történik, tehát a két regiszter között logikai műveletet végeztetünk, akkor az első bitek határozzák meg az eredmény első bitjét, a második bitek az eredmény második bitjét, stb. A három logikai művelet a következő:
Milyen regiszterek állnak rendelkezésünkre? A Z80A tizennyolc darab 8 bites, és négy darab 16 bites regiszterrel rendelkezik. A regisztereket két csoportra oszthatjuk: általános és speciális regiszterek.
A speciális regiszterek a következők:
Az általános célú regiszterek nyolcbitesek, az utasítások ki- és bemenetét jelentik. Az a feladatuk, hogy az egyes utasítások bemenő adatait, pl. összeadandókat, összehasonlítandó értékeket a végrehajtás idejére tárolják; az eredmények is itt kerülnek tárolásra. A regiszterek jelei: B, C, D, E, H, L. A regiszterek három párrá alakíthatók, a BC, DE és HL párok 16 bites regiszterként használhatók. A különbséget az egyes regiszterek között az jelenti, hogy nem minden művelet alkalmazható minden általános célú regiszterre. A HL regiszterre néhány 16 bites aritmetikai operáció alkalmazható, ezért gyakran használják egy memóriacím tárolására. Jele is innen ered (HL= high-low; nagy-kicsi). A BC regiszter) a BASIC programokhoz való kapcsolat szempontjából kiemelkedő fontosságú, mivel a BASIC-ből könnyen elérhető.
Az akkumulátorból, és a hat általános célú regiszterből még egy van, ezeket A', B', C', D', E', H' ill. L' betűkkel jelöljük. Az eredeti regiszterkészlet egy egyszerű csereutasítás hatására felcserélődik a háttérkészlettel. Ez gyakorlatilag két program felváltva való futtatására ad lehetőséget, de arra is jó, hogy a regiszterekben tárolt adatokat a saját gépi kódú programunk futási idejére elrakjuk.
Hogy fut a gépi kódú program? A mikroprocesszor kiolvassa a PC regiszterben a megadott című memóriacellából a következő utasítást kijelölő értéket, végrehajtja a feladatot, növeli PC értékét, majd előveszi a program következő lépését, és így tovább. Ezeket a műveleteket a processzor saját mikroprogramja vezérli egy ütemadó órajellel adott sebességgel. A ZX81 órajele 3.25 MHz-es, ami annyit jelent, hogy másodpercenként 3.25 millió belső művelet zajlik le. Ez átlagosan félmilló gépi kódú utasítás végre hajtását jelenti. A gépi kódú program futtatásához a memória egymás utáni byte-jait fel kell töltenünk a megfelelő számokkal, majd a PC regisztert a kezdőcímre állítjuk, és már fut is a program.
Problémát már csak a megfelelő gépi kódú program megírása jelenthet. A gépi kódú utasítások nem értelmes szavak, hanem számok, hiszen a mikroprocesszor csak "0" és "1" megkülönböztetésére képes. Igen kényelmetlen lenne azonban, ha csak kettes számrendszerben ábrázolt, un. bináris számokat használhatnánk. Ezen olyan konvertáló-programokkal segíthetünk, amelyek a bináris kódokat praktikusabb számrendszerbe teszik át, ill. binárissá alakítanak más számrendszerbeli számokat. A ZX81 POKE-PEEK párja is ilyen, hiszen a PEEK az adott byte bináris tartalmát alakítja tízes számrendszerűvé, a POKE pedig visszafelé konvertál. A bináris és decimális számokon kívül gyakran használjuk a 16-os (hexadecimális) számrendszert, a Z-80 kódjait is többnyire így adják meg.
Mivel a gépi kód tanulásakor sokan már a különböző számrendszereknél megakadnak, ennek a témának külön fejezetet szentelünk. Ha valaki jártas már ebben, lapozza nyugodtan át.
IV./3. A decimális, bináris és hexadecimális számrendszer
A számrendszereket az egymástól különböző számjegyek mennyisége határozza meg. Így a decimális számrendszerben tíz, a binárisban két, a hexadecimálisban tizenhat számjegy van. Minden számrendszer helyértéket tulajdonit a számjegyek elfoglalt helyének. A szám értékét az egyes jegyek helyértékszeresének összege adja. A helyérték kiszámításához pedig a rendszer alapszámát kell arra a kitevőre emelni, ahányadik az adott számjegy a szám utolsó jegyétől számítva.
Pl. tízes számrendszerben:
23456 = 2*10^4 + 3*10^3 + 4*10^2 + 5*10^1 + 6*10^0
kettes számrendszerben:
1010011011 = 1*2^9 + 0*2^8 + 1*2^7 + 0*2^6 + 0*2^5 + 1*2^4 +1*2^3 + 0*2^2 + 1*2^1 + 1*2^0
Tizenhatos számrendszerben, ahol a számjegyekből tizenhat van (0 1 2 3 4 5 6 7 8 9 A B C D E F), Pl. a
2468ACE = 2*16^6 + 4*16^5 + 6*16^4 + 8*16^3 + 10*16^2 + 12*16^1 + 14*16^0
Arra, hogy egy számot 16-os számrendszerben irtunk, a végére irt "H" betűvel hívjuk fel a figyelmet. Mivel ilyen számjegy nincs, ez nem okozhat zavart.
Nézzünk meg összehasonlításul néhány számot különböző számrendszerekben felírva:
decimális |
hexadecimális | bináris |
0 |
0 |
0 |
1 |
1 |
1 |
2 |
2 |
10 |
3 |
3 |
11 |
4 |
4 |
100 |
5 |
5 |
101 |
6 |
6 |
110 |
7 |
7 |
111 |
8 |
8 |
1000 |
9 |
9 |
1001 |
10 |
A |
1010 |
11 |
B |
1011 |
12 |
C |
1100 |
13 |
D |
1101 |
14 |
E |
1110 |
15 |
F |
1111 |
16 |
10 |
1 0000 |
17 |
11 |
1 0001 |
... |
... |
... |
127 |
7F |
111 1111 |
128 |
80 |
1000 0000 |
129 |
81 |
1000 0001 |
... |
... |
... |
254 |
FE |
1111 1110 |
255 |
FF |
1111 1111 |
Ha nullánál kisebb számok ábrázolására is szükség van, akkor egy bitet az előjelre kell felhasználni. Erre a célra a legnagyobb helyértékű bitet használják úgy, hogy ha az előjelbit 1, akkor negatív a szám, ha pedig 0, akkor pozitív. Előjelesen viszont csak feleannyi számot tudunk ábrázolni, hiszen az első bitet az előjel foglalja el. Tehát, ha összesen nyolc bit áll rendelkezésre, akkor csak maximum hét bites bináris számmal mérhető az érték. Hét bittel 0 és 127 közötti számok fejezhetők ki, ebből következik, hogy a nyolc bittel -128 és 127 közti intervallumban irhatok le számok. Megállapodás szerint így:
decimális |
hexadecimális | bináris |
-128 |
80H |
1000 0000 |
-127 |
81H |
1000 0001 |
-126 |
82H |
1000 0010 |
... |
... |
... |
-64 |
C0H |
1110 0000 |
-63 |
C1H |
1110 0001 |
... |
... |
... |
-1 |
FFH |
1111 1111 |
0 |
00H |
0000 0000 |
1 |
01H |
0000 0001 |
... |
... |
... |
31 |
1FH |
0001 1111 |
32 |
20H |
0010 0000 |
... |
... |
... |
127 |
7FH |
0111 1111 |
Arra a kérdésre, hogy egy byte-ban vajon egy negatív avagy egy 127-nél nagyobb pozitív szám van, csak az tudja a választ,aki beírta az értéket.
0 és 128 közötti bináris számokat a következő két lépésben alakíthatunk át ugyanolyan abszolút értékű negatívvá:
Néhány példa az átalakításra:
74 = 0010 1010
4 = 0000 0100
10 = 0000 1010-74 = 1101 0110
-4 = 1111 1100
-10 = 1111 0110
A hexadecimálisan ábrázolt negatív számok vagy a bináris számrendszeren keresztül határozhatók meg, vagy pedig 256-ból kell kivonni a keresett szám abszolút értékét, és ezt az eredményt kell hexadecimálissá alakítani.
Már az eddig elmondottakból is látható, hogy sokszor szükséges a különböző számrendszerek egymásba alakítása. Az ilyen feladatok egyszerűen megoldhatók számítógéppel, erre BASIC programokkal mutatunk példát, e fejezet végén.
A decimális számokra való alakításnál csupán az átalakításra váró szám jegyeinek helyértékszeresét kell összeadogatni, ahogy ez a számrendszerek bevezetésénél szerepelt. A decimálisról más számrendszerekbe történő átalakítás kicsit bonyolultabb ennél. Most csak a leggyakrabban előforduló esettel, a 0 és 255 közötti számok konverziójával foglalkozunk.
Ha tízes számrendszerből kell hexadecimálisra áttérni, akkor először a decimális alakot 16-tal kell elosztani, ekkor az eredmény egész része lesz a hexadecimális szám első jegye, és a törtrész 16-szorosa a második. Így:
234 /16 = 14.625 14=EH 16*0.625 = 10 10=AH tehát 234 = EAH Vissza is ellenőrizhetjük: 14*16+10=234.
A decimális bináris konvertáláshoz a kettő hatványait (2^7-töl 2^0-ig) kell rendre összehasonlítani a decimális számmal. Ha a hatvány nagyobb a számnál, akkor leírunk egy nullát, ha nem, akkor egy 1-est, és ugyanakkor a hatványt kivonjuk a számból; csökkentjük a kitevőt és megismételjük az előbbi folyamatot, amíg 2^0-ig nem érünk. Egy példa:
163-2^7 = 35 7. BIT=1 35 < 2^6 6. BIT=0 35-2^5 = 3 5. BIT=1 3 < 2^4 4. BIT=0 3 < 2^3 3. BIT=0 3 < 2^2 2. BIT=0 3-2^1 = 1 1. BIT=1 1-2^0 = 0 0. BIT=1 Tehát 163 = 10100011.
Hátra vannak még a bináris és a hexadecimális számok közötti átalakítások. Hexadecimális számot igen kényelmesen, számjegyenként konvertálhatunk binárissá. Annyi a teendő, hogy az egyes számjegyeknek megfelelő bináris számnégyeseket egymás mellé írjuk. Néhány példa:
ABH= 1010 1011 (A=1010, B=1011) 02H = 0000 0010 (0=0000, 2=0010) FEH = 1111 1110 (F=1111, E=1110) Ha bináris-hexadecimális átalakításra van szükség, akkor sem nehezebb a dolog, csak a bináris számot jobbról kezdve négyes csoportokra kell felosztani, és ezek hexadecimális kódjait (egy-egy számjegy) egymás mellé írva már el is végeztük a feladatot. Erre az átalakításra is nézzünk néhány példát:
10101010 = 1010 1010 = AAH
00111011 = 0011 1011 = 3BH
10010001 = 1001 0001 = 91H
Az egyszerű átalakítás oka természetesen az, hogy 2^4=16, így a tizenhatos számrendszer "határszámai" a kettes számrendszerben is helyértékhatárok.
A különböző számrendszereket könnyen konvertálhatjuk számítógéppel. Erre mutatnak példát a következő BASIC programok.
a) Hexadecimális-decimális konverzió
4 PRINT "ADJ MEG EGY HEXADECIMALIS KETJEGYU SZAMOT"
6 INPUT A$
8 LET A=16*CODE A$(1)+CODE A$(2)-476
10 PRINT A
12 STOP
b) Decimális-hexadecimális konverzió
2 DIM H$(2)
4 PRINT "ADJ MEG EGY DEGIMALIS SZAMOT 0-255-IG"
6 INPUT D
8 LET D1=INT(D/16)
10 LET D2=D-16*D1
12 LET H$(1)=CHR$(28+D1)
14 LET H$(2)=CHR$(28+D2)
16 PRINT H$
18 STOP
c) Decimális-bináris konverzió
4 DIM B$(8)
6 LET B$="00000000"
8 PRINT "ADJ MEG EGY DECIMALIS EGESZ SZAMOT 0-TOL 255-IG"
10 INPUT D
12 LET X=256
14 FOR S=7 TO 0 STEP -1
16 LET X=X/2
18 IF D/X=1 THEN GOSUB 40
20 NEXT S
22 PRINT B$
24 STOP
40 LET B$(8-X)="1"
42 LET D=D-X
44 RETURN
d) Bináris-decimális konverzió
4 PRINT "ADJ MEG EGY BINARIS SZAMOT 0-TOL 11111111-IG"
6 PRINT AT 20,0;"BITEK"
8 PRINT " 76543210"
10 INPUT B$
12 IF LEN B$<>8 THEN GOTO 4
14 LET X=256
16 LET A=0
18 FOR 5=7 TO 0 STEP -1
20 LET X=X/2
22 IF B$(8-S)="1" THEN LET A=A+X
24 NEXT S
26 PRINT AT 1,1;A
A programok menetére most külön nem térünk ki.
IV./4. A Z-80 utasításai
A mikroprocesszor számára az utasításokat számokkal adjuk meg. Mivel a hosszú számtáblázatokkal való közvetlen munka szinte lehetetlen, a programozás megkönnyítésére kidolgozták az ún. ASSEMBLER (vagy más néven assembly) nyelvet, amely minden gépi kódnak egy angol szó rövidítését felelteti meg, a szó természetesen az utasítás funkciójára utal. E rövidítésekkel, amelyeket mnemonikoknak nevezünk, könnyen dolgozhatunk, és a program elkészültekor táblázatok segítségével vagy magával a számítógéppel gépi kódokra fordíthatjuk azt. Mivel a gépi kód és az ASSEMBLER között egy-egy értelmű a kapcsolat, az odavissza fordítás egyértelmű, csupán mechanikus munka. Az assembly nyelvet gépi kódra fordító programot nevezik ASSEMBLER-nek, a visszafordító programot pedig DISASSEMBLER-nek.
A teljes utasítástáblázatot a 3. függelék tartalmazza. Vegyük sorra a Z-80 utasítástípusait! Az utasításokat - a bevezetőben említett felosztásnál kicsit finomabban csoportosítva- a következő hat csoportra bonthatjuk:
a) Adatmozgató (töltő-) utasítások
Az adatok alapvető mozgatását az "LD" (LOAD) jelöli, azt hogy honnan, hová történjen az adatmozgatás, a LD után következő kódok döntik el: pl. LD A,B jelentése: töltsd az akkumulátorba B tartalmát! Ez a BASIC LET A=B-vel rokon, csupán az a különbség, hogy míg a BASIC-ben a művelet két változót tesz egyenlővé, addig a gépi utasítás az "A" fizikailag létező regiszter tartalmát tölti a hasonlóan "megfogható" B-regiszterbe.
Az előbbi példában szereplő "LD"-t az utasítás mnemonikus részének, az "A,B"-t pedig az operandus részének nevezik. A mnemonikus kód jelöli ki az utasításfajtát, az operandus pedig meghatározza azokat a regisztereket, amelyeken a műveletet el kell végezni. Ez a felépítése a nyelv legtöbb utasításának, bár adódnak olyan kivételek, amelyeknek nincs operandusa.
Az operandusban a következő módokon határozható meg, hogy mire vonatkozzék a művelet:
Látható, hogy a zárójel az ASSEMBLER nyelvben azt jelenti, hogy az itt megadott szám annak a memóriarekesznek a címét adja, amivel a tényleges műveletet el kell végezni.
b) Logikai és aritmetikai utasítások
Ezek az utasítások végzik a matematikai műveleteket. Végezhetünk velük összeadást, kivonást vagy különböző logikai műveleteket a regiszterek tartalmai között. Néhány példa:
ADD A,B - az akkumulátorhoz hozzáadja B értékét, az eredmény az akkumulátorba kerül; SBC A,(HL) - kivonja A-ból a HL által meghatározott memória tartalmát (SUBSTRACT utasítás); AND B - logikai AND kapcsolatot létesít az A (akkumulátor) és a B regiszter között.
c) Elágaztató utasítások, szubrutinok
A BASIC-hez hasonlóan a gépi nyelv is használ elágaztatásokat. Segítségükkel módosítható a programszámláló tartalma, így a program a PC új értékének megfelelő memóriabyte-tól folytatódik. Az elágaztató utasításoknak két fajtáját ismeri a Z-80-as:
Mind a kétfajta utasítás végrehajtása köthető bizonyos, a jelzőbitek által meghatározott feltételekhez. Így pl:
Szubrutinok hívására a CALL, szubrutinból való visszatérésre a RET (RETURN) utasítás szolgál. Pl:
Ezek az utasítások is feltételekhez köthetők, pl:
CALL Z,4567
RET M
d) A veremtárolót (stack) kezelő utasítások
A Z-80 alkalmas egy ún. veremtár kezelésére A verem. elnevezés találóan jelzi, hogy a tárolóba betett adatok közül mindig csak a legfelső (tehát a legutóbb berakott) vehető elő. Ha a veremtárban akarjuk elhelyezni valamelyik kétbyte-os regiszter tartalmát, akkor a PUSH, ha pedig a veremtárból akarjuk a legfelső két byte-ot a kétbyte-os regiszterek valamelyikébe tenni akkor a POP mnemonikat használjuk, pl:
PUSH BC
POP BC
Ugyanezt a veremtárat használja a CALL-RET utasításpár is a visszatérési cím tárolására, ezért ügyelni kell arra, hogy a szubrutinban minden POP-nak legyen PUSH párja és viszont, mert különben nem lesz helyes a visszatérési cím, és eltéved a program.
A veremtár tetejének mindenkori címét tartalmazza az SP-regiszter, amivel szükség esetén műveletek is végezhetők.
e) Ki- és bevitel
A számítógép perifériáira küldenek ki, ill. onnan hoznak be adatokat a következő utasítások:
IN A,ED - az ED című perifériáról tölti át az adatot az akkumlátorba; OUT D4,A - az akkumulátor tartalmát viszi ki a D4 című perifériára.
Egy kis magyarázatot igényel, hogy mit jelent az ED, D4 vagy egyéb című periféria. Ez a mikroprocesszor szempontjából azt jelenti, hogy pl. OUT esetén a címbusszal kijelöli a perifériát, az adatbuszra kirakja a megfelelő adatot (ill. IN esetén beolvassa), a többi a kiszolgáló áramkörök dolga. A ZX81 esetében szinte minden esetben a képernyő és a "MIC" kimenet egyszerre működik, és csak a használt jelek frekvenciája különbözik. Ezért láthatók pl. csíkok a képernyőn a kiírás és beolvasás ideje alatt. Az utasításban megadott címek a ZX81-ben F0-nál nagyobbak.
f) Operandus nélküli utasítások
Vannak olyan utasításai a Z-80-nak, amelyeknek nincs operandusa. Ezek speciális műveleteket vagy műveletsorozatokat jelölnek ki.
V. Gépi kódú programozás a ZX81-en
V./1. A program bevitele, futtatása
A ZX81-en futó, általunk írt gépi programokat ugyanaz a mikroprocesszor futtatja, amelyik a gép összes többi funkcióját is ellátja. Ezért programjainkban ügyelni kell arra, hogy ne zavarjuk meg az alapprogram működését saját programunkkal, ami az alaprendszer szubrutinjaként fut. Ez a gyakorlatban a következő megszorításokat jelenti:
Ez persze nem minden. A gépi kódú program írásakor sokkal figyelmesebben kell dolgozni, mint BASIC-ben. Gépi kódban ugyanis nincs hibaüzenet, ha egy program nem működőképes, akkor "elszáll". A gépi kódú program futása alatt nem tudunk beavatkozni a gép működésébe (nincs BREAK), így ha rossz a program, csak a kikapcsolás segít. Ezért minden esetben még a lefuttatás előtt vegyük magnóra a beírt programot, így sok felesleges munkától kímélhetjük meg magunkat.
Most már eleget tudunk ahhoz, hogy elkezdjük első gépi kódú programunk elkészítését. A feladat igen egyszerű: a 16507-es byte-ba 7-et akarunk írni. Ez BASIC-ben így nézne ki: POKE 16507,7. Gépi kódban a megoldás hosszabb ennél, de mivel a gépi kódú programokban gyakran előfordul egy memóriarekesz feltöltése, érdemes ezzel külön is foglalkoznunk.
A megoldás a következő:
LD A,07
LD (407B),A
RET; az akkumulátorba 7-et ír
; a 407B című byte-ba tölti
; A tartalmát (16507=407BH)
; visszatér BASIC-be
Miután a programmal készen vagyunk, következhet a fordítás. Akinek van ASSEMBLER programja, annak ezt a lépést nem kell elvégezni, hiszen a géppel fordíttathatja le a programját. Ennek hiányában viszont elő kell vennünk a 3. függelék fordítótáblázatait. A fordítás a következő módon történik:
LD A,07 a fordítótáblázatban nincs, ellenben van "LD A,byte", aminek a gépi kódolt megfelelője 3Ebyte. (byte - egy byte-os adat) Ha a "byte" helyére beírjuk a 07-et már meg is van a keresett gépi kód: LD A,07 = 3E07.
Az LD (407B),A utasítással is hasonló eljárást kell követnünk: "LD (word),A" kódja "32word" (word - 2 byte-os adat), itt is az "word" helyére kell a konkrét értéket (407B) írni, csak arra kell ügyelni, hogy a két byte-os adatokat a két byte felcserélésével írjuk az utasításba, mert a mikroprocesszor így várja őket. Tehát a második utasítás lefordítva a következő: LD (407B),A = 327B40.
A RET utasítás fordítása (a táblázat segítségével): C9.
A programunknak tehát a következő hexadecimális számsor felel meg:
3E, 07, 32, 7B, 40, C9
A program futtatása azt jelenti, hogy ez a számsort egymás után következő memóriarekeszekbe helyezzük el, és a programszámláló regisztert (PC) az első memória címére állítjuk. Itt újabb probléma adódik, hová kerüljön a program?
Olyan biztonságos helyet kell keresni a memóriában, ahol az alapprogram nem tesz kárt a mienkben, ill. fordítva. Ilyen hely lehet a RAMTOP feletti terület (ekkor a RAMTOP rendszerváltozót kell POKE utasítással megváltoztatni), de lehet egy REM utasítás szövegmezője is, ami ugyanis egy REM utasítás után következik, azzal a mikroprocesszor a BASIC program futásakor egyáltalán nem foglalkozik. Példaprogramjaink elhelyezésénél ezt az utóbbi megoldást választjuk, ekkor ugyanis nem kell a RAMTOP beállításával törődnünk, és a programunk is együtt SAVE-elhető a BASIC résszél. A legokosabb mindjárt a BASIC program legelejére tenni a gépi kódú programot tartalmazó REM utasítást, mert az első utasítást tartalmazó memóriák címe egyértelmű, míg a többi utasítás helye az előtte álló utasítások hosszától függ.
A ZX81 memóriájának felépítését ismerve tudjuk, hogy az első BASIC programsor a 16509 című byte-tól kezdve található a memóriában. Ha a BASIC program első utasítása:
1 REM 234567890
akkor az utasításban szereplő "2"-es a 16514, a "3"-as a 16515, a "0" pedig a 16522 című byte-ot foglalja el (a 16509-16513 byte-okban maga az utasítás, sorszáma és hossza van.) Ezek alapján a következő módon járjunk el:
Sajnos a beíráshoz decimális számokká kell alakítani a hexadecimális kódokat, mert a POKE utasítás csak decimális alakban fogad el számokat. Az imént megírt programunk beírása tehát így történik:
3EH = 62
07H = 7
32H = 50
7BH = 123
40H = 64
C9H = 201->
->
->
->
->
->POKE 16516,62
POKE 16517,7
POKE 16518,50
POKE 16519,123
POKE 16520,64
POKE 16521,201
Ha ezt elvégeztük, a BASIC programot kilistázva a REM utasításban lévő első hat szám helyett egész más karakterek lesznek. Ez mutatja, hogy jó helyre POKE-oltuk be a kódokat.
Most már elindítható a gépi program. A program indítása BASIC-ből az USR függvénnyel történhet. Az USR n hatására a mikroprocesszor lefuttatja a gépi kódú programot az n című memóriarekesznél kezdve, és amint a RET gépi kódú utasításhoz ér (aminek nincs CALL párja), visszatér a BASIC-be. Az USR függvény értéke a BC regiszterpár tartalma lesz. Az USR függvény, tehát a BASIC szintaktika szerint nem írhatjuk utasítás helyett. Legtöbbször a RAND USR kifejezést használják (a RAND-nak csak kitöltő szerepe van), de a RAND helyett szerepelhet a PRINT, vagy akár a LET A= is. Futtassuk le tehát programunkat! Adjunk ki
RAND USR 16514
Utasítást! A futtatás után két eset lehetséges:
Mivel első programunk célja az volt, hogy a 16507 cimű memóriába 7-et rakjunk, ha sikerült a manőver, a PRINT PEEK 16507 utasítás hatására a képernyőn a hetes számnak kell megjelennie, bizonyítva, hogy nem dolgoztunk hiába.
A gépi kódú program itt leirt módon való bevitele elég körülményes. Ezt a munkát könnyíti meg a következő BASIC program.
Monitorprogram (8 K RAM szükséges)
1 REM ..........................................................................
2 DIM B$
3 REM EZEK A PONTOK FOGLALNAK HELYET A PROGRAMNAK,AHANY PONT VAN ANNYI BYTE-OS LEHET A GEPI NYELVU PROGRAM
4 CLS
6 FOR I=16516 TO 16738
7 LET P=16516
8 GOSUB 700
15 INPUT A$
16 GOTO 20+(-5 AND LEN A$<>2)+ (80 AND CODE A$=47)+(170 AND CODE A$=49)+(380 AND CODE A$=58+(880 AND CODE A$=45)
25 POKE I,CODE A$(1)*16+CODE A$(2)-476
30 PRINT A$;" ";
40 NEXT I
100 PRINT AT 21,0;"SOR?"
102 INPUT S
105 PRINT AT 21,0;"OSZLOP"
108 INPUT 0
109 PRINT AT 21,0;" "
116 LET I=16516+8*(S-1)+O-1
117 LET J=INT (S/22)
119 IF J>=22 THEN SCROLL
128 PRINT AT S-1-22*INT (S/22), (O-1)*3+6
130 GOTO 15
190 CLS
191 PRINT AT 21,0;"HANYADIK BYTE-TOL?"
192 INPUT H$
202 CLS
204 LET 0=16516
205 IF H$="" THEN GOTO 207
206 LET 0=VAL H$
207 LET P=0
208 FOR O=0 TO 66000
209 LET A=PEEK 0
210 GOSUB 810
213 LET I=O
214 GOSUB 700
216 IF CODE INKEY$=27 THEN GOTO 216
219 IF CODE INKEY$=42 THEN GOTO 15
220 PRINT B$;" ";
224 NEXT O
400 PRINT AT 20,0;"SAVE ?"
406 INPUT S$
408 IF S$="N" THEN GOTO 420
410 PRINT AT 20,0;"NEV ? "
412 INPUT N$
414 IF N$="" THEN GOTO 412
418 SAVE N$
420 PRINT AT 20,0;"RUN USR ?"
422 INPUT S$
424 IF S$=N THEN GOTO 4
426 PRINT "CIM ? (16516)"
428 INPUT U
430 RAND USR U
440 GOTO 4
704 LET H=(I-16516)/8
708 IF INT H<>H THEN RETURN
709 FAST
710 LET O$=STR$ I
711 IF H=0 THEN GOTO 716
712 IF H/22=INT (H/22) THEN GOSUB 800
714 IF H/22=INT (H/12) THEN CLS
718 LET D$="00000"
724 LET D$(6-LEN O$ TO 5)=O$
740 PRINT TAB 0;D$;TAB 6;
745 SLOW
760 RETURN
800 IF CODE INKEY$<>27 THEN GOTO 800
802 RETURN
812 LET A1=.2
816 IF A<256 THEN GOTO 830
818 LET A=INT (A/256)
820 LET A1=D-A*256
830 LET D1=INT (A/16)
835 LET B$(1)=CHR$ (D1+28)
840 LET B$(2)=CHR$ (A-16*D1+28)
850 IF A1=.2 THEN RETURN
852 LET A=A1
854 LET M$=B$
856 GOTO 812
860 RETURN
900 INPUT D
903 LET A=D
910 LET M$=" "
920 GOSUB 810
930 PRINT AT 21,1;G;"=";M$;B$;"H";" "
935 INPUT Z$
940 GOTO 4
Ezzel a programmal kiküszöbölhetőek a decimális-hexadecimális átalakítás és a POKE-olás hibái. Az előbbi procedúrából csak az ASSEMBLER-gépi kód fordítást kell továbbra is papíron végezni, és ez nem kis könnyebbség. A program a beíráson kívül alkalmas a memória tetszés szerinti területének hexadecimális formában való megjelenítésére, javításra, át tud alakítaní decimális számokat hexadecimálissá, és a kész program elindításában is segít. A lefordított assembly utasításokat byte-onként kell beadni hexadecimális alakban. A kódok nyolcasával, sorokba rendezve jelennek meg a képernyőn. Minden sor előtt ott áll a sor első kódjának címe, ennek segítségével könnyen kiszámítható bármelyik byte helye.
A program egykarakteres parancsokat is elfogad. Ezek a következő betűk lehetnek:
V./2. A képernyő kezelése
Most lássunk néhány, még mindig egyszerű példát a gépi nyelv használatára! Ezek önállóan nem használható programok, de mint majd látni fogjuk, ilyen egyszerű kis részek összekapcsolásából fognak megszületni a látványos programok is.
Azokat a programrészeket, amelynek megértése esetleg problémát jelent, BASIC lépésekkel magyarázzuk. Ezek a párhuzamok sokszor nem tökéletesek, ennek ellenére sok segítséget nyújthatnak.
Tegyük fel, hogy a képernyő bal felső sarkába szeretnénk tenni a C karaktert. Ehhez ismerni kell a D-FILE rendszerváltozó értéket, ami megmutatja hol kezdődik a képet tároló memóriaterület. A D-FILE-ban talált értéket eggyel megnövelve megkapjuk az első, tehát a bal felső sarokban látható karakter kódját tároló címet. Ide kell a C kódját (28H) beírni. Így a megoldás hasonló az előző feladatéhoz:
2A0C40
23
3628
C9LD HL,(4000C)
INC HL
LD (HL),28
RET; -D-FILE
; HL=HL+1
; 28H=CODE "C"
; vissza BASIC-be
E programból kiindulva a kép bármely részébe írhatunk karaktert, csak azt kell kiszámolni, hogy mi az adott memória címe. A könyv első fejezetéből tudjuk, hogy egy sor 33 byte-ot foglal el (32 karakter + NEWLINE), ez hexadecimálisan 21H, így a számolás nem nagy gond. A cím kiszámításánál ügyeljünk arra, hogy a D-FILE rendszerváltozó nem a képernyő első karakterét tároló memória címét adja, hanem annál eggyel kevesebbet.
A következő program a hatodik sor közepére tesz egy K-t:
2A0C40
23
112100
19
19
19
19
19
110F00
19
3630
C9LD HL,(400C)
INC HL
LD DE,0021
ADD HL,DE
ADD HL,DE
ADD HL,DE
ADD HL,DE
ADD HL,DE
LD DE,000F
ADD HL,DE
LD (HL),30
RET; D-FILE
; első karakter
; 2. sor
; 6. sor
; 0F sor közepe
; 6. sor közepe
; 30H = CODE "K"
; vissza BASIC-be
E programnál is látható, hogy a HL regiszterpárban célszerű memóriacímet tárolni, mivel az ADD utasítás e regiszterekre is értelmezett, így növelni tudjuk az itt tárolt értéket anélkül, hogy az akkumulátorhoz nyúlnánk.
Az előző feladatot oldja meg a következő program is, a különbség csak abban van, hogy az összeadásokat itt ciklusban végezzük:
2A0C40
23
112100
0604
19
05
20FC
110F00
19
3630
C9
VISZLD HL,(400C)
INC HL
LD DE,0021
LD B,4
ADD HL,DE
DEC B
JR NZ,VISZ
LD DE,000F
ADD HL,DE
LD (HL),30
RET; D-FILE
; hanyszor?
; B = B-1
; ha B<>0 VISZ
; vissza BASIC-be
A program a harmadik lépésig azonos az előzővel, ezért ezzel a résszel nem foglalkozunk. Lássuk a ciklus BASIC megfelelőjét:
4 LET B=4
5 LET HL=HL+DE
6 LET B=B-1
7 IF B<>0 THEN GOTO 8+(-3)
E pár BASIC utasítás jól mutatja a működés elvét, azonban a 7. sorban találunk egy furcsa dolgot. A GOTO 8+(-3) próbálja kifejezni azt, hogy miként dolgozik a JR utasítás. A JR ugyanis nem fix, az utasításban megadott címre ugrik, hanem a gépi kódban meghatározott távolságra levő byte-on folytatja a programot. Ezt a távolságot a mikroprocesszor az ugróutasítást követő byte-tól számolja előre, vagy hátra, a távolságot jelölő szám előjelétől függően.
Az ASSEMBLER programlistába a relatív ugrás kiszámításának megkönnyítésére jelzéseket (címkéket) rakhatunk - jelen esetben a VISZ jelzést használtuk. Ez nem fordítható, csak a programban való eligazodást segíti. Programunkban a JR NZ,VISZ a 10FC gépi kódnak felel meg, mivel a VISZ címkével jelölt utasítás első byte-ja a negyedik az ugróutasítást követő byte-tól számítva. Az ugrás hátrafelé történik ezért a négyből, mínusz négy lesz, ami pedig FCH-val egyenlő. Az ilyen ugrások számítását a lehető leggondosabban végezzük, mert a legapróbb tévedés is elég ahhoz, hogy a program eltévedjen, és ritkán fordul elő, hogy egy eltévedt program kontrollálható marad.
Az itt következő program még egyszerűbben oldja meg az előző feladatot a DJNZ XX ciklusszervező utasítással.
2A0C40
23
112100
0604
19
10FD
110F00
19
3630
C9
VISZLD HL,(400C)
INC HL
LD DE,0021
LD B,4
ADD HL,DE
DJNZ VISZ
LD DE,000F
ADD HL,DE
LD (HL),30
RET; D-FILE
; hanyszor?
; ha B<>0 akkor VISZ
; vissza BASIC-be
A DJNZ XX tulajdonképpen a DEC B és a JR NZ XX utasítások feladatát végzi el egymaga. Természetesen a DJNZ XX-re is érvényesek a relatív ugrás szabályai.
V./3. Adatcsere ASSEMBLER és BASIC között
A gépi kódú programokat többnyire BASIC programok alprogramjaként használjuk. Ilyenkor a gépi kódú szubrutinnak szüksége van a BASIC programban kiszámított paraméterekre, és az eredményt is elérhetővé kell tenni a BASIC program számára.
Ha egy adatot kell átadni a gépi nyelven írt részbe, a legegyszerűbb, ha egy kiválasztott byte-ba POKE-oljuk az értéket, ahol azután a gépi program könnyen elérheti.
Amikor a BASIC programba szükséges adatot kell átvinni, akkor szintén járható ez az út (természetesen fordítva, tehát a PEEK használatával), de itt van gyorsabb megoldás is. Már volt róla szó, hogy a USR XX egy olyan speciális függvény, amely elindítja a megadott gépi programot, és a BASIC-be való visszatérés után függvényértéknek a BC regiszter tartalmát adja. Tehát a PRINT USR 16516 utasítás a 16516-os címen található program futtatása után a BC regiszterbe töltött számot írja ki.
Nem árt elismételni, hogy a USR függvény használható gyakorlatilag mindenütt, ahol más függvény előfordulhat. Így többek között értelmes a
GOTO USR XX
LET A=USR XX
PRINT USR XX
PLOT USR XX, USR YY
RAND USR XX
PAUSE USR XX
A RAND USR XX-nek semmilyen látható hatása nincs, ezért ezt használjuk, ha a BC tartalma nem érdekes. A következő programok konkrétan mutatják az adatcserét:
gépi kódú rész:
1802
0000
218640
110900
19
44
4D
C9
ELO
JR ELO
0000
LD HL,(4086)
LD DE,0009
ADD HL,DE
LD B,H
LD C,L
RET; adatbyte átugrása
; adatbyte
; adat HL-be
; hozzáadandó
; összeadás
; BC-be HL
; vissza BASIC-be
BASIC rész:
10 INPUT A
20 POKE 16518,A
40 LET A=USR 16516
45 PRINT A
50 G0T0 10
A 40-45. utasítások helyett írhatunk PRINT USR 16516 utasítást is.
A gépi kódú program igen egyszerű: a 16518-as byte-ba helyezett számhoz kilencet ad hozzá. Látható, hogy a BASIC az adatot POKE utasítással adja meg, az eredményt a USR függvény értékeként kapjuk.
Ezzel a módszerrel annyi függvényt készíthétünk, ahányat csak akarunk; a függvényeket a USR-ben megadott kezdőcímekkel tudjuk megkülönböztetni.
V./4. További példák
A gépi kódú programozás alapjainak elsajátítása után lássunk hozzá az összetettebb példákhoz. Az alábbi program már látványosabb az előzőeknél, ennek ellenére nem sokkal bonyolultabb azoknál. Az a feladata, hogy bekeretezze a képet. Ezt négy különálló rész egymás után végzi, egy rész egy oldalt húz meg. A négy rész szétválasztható, egyenként megérthető, és ki is próbálható. Valójában mind a négy rész egy kaptafára készült: először meghatározzák a keret egyes vonalainak kezdetét, majd adott számú karaktert meghatározott távolságokban beírnak a memóriába.
Az első rész BASIC-ben így nézne ki:
10 LET A=PEEK 16396+256*PEEK 16397
20 FOR B=31 TO 1
30 LET A=A+1
40 POKE A,22
50 NEXT B
A teljes ASSEMBLER program a következő:
2A0C40
061F
23
3616
10FB2A0C40
2B
112100
0618
19
3611
10FB2A0C40
11FB02
19
061F
3616
23
10FB2A0C40
23
112100
0618
3610
19
10FB
C9
FENT
JOBB
LENT
BALLD HL,(400C)
LD B,1F
INC HL
LD (HL),16
DJNZ FENTLD HL,(400C)
DEC HL
LD DE,21
LD B,18
ADD HL,DE
LD (HL),11
DJNZ JOBBLD HL,(400C)
LD DE,02F8
ADD HL,DE
LD B,1F
LD (HL),16
INC HL
DJNZ LENTLD HL,(400C)
INC HL
LD DE,21
LD B,18
LD (HL),10
ADD HL,DE
DJNZ BAL
RET; D-FILE
; keretszélesség
; következő hely
; CHR$ 16H="-"
; vissza, amíg B nem 0; D-FILE
; keretmagasság
; következő hely
; CHR$ 11H=")"
; D-FILE
; a legalsó sor
; számítása
; keretszélesség
; CHR$ 16H="-"
; következő hely
; D-FILE
; sorhossz+1 (NEWLINE)
; keretmagasság
; CHR$ 10H="("
; következő hely
; vissza BASIC-be
Ha ezt a programot kipróbáljuk, rögtön látható lesz, mennyivel gyorsabban oldhatunk meg grafikus feladatokat, ha BASIC helyett gépi nyelven írjuk az ilyen részeket. Nézzük mi a teendő, ha a képet tele akarjuk rajzolni karakterekkel! Alapjában véve ez nem jelent új problémát, hiszen vonalat már tudunk húzni. Tehát ugyanazt kell csinálni, mint a "Keretező" program felső vonalat rajzoló része tesz, csupán a ciklus számlálóját kell átállítani akkorára, hogy a vonal az összes sort kitöltse. Azonban ügyelni kell arra, hogy a memóriában a sorokat elválasztó 76H kódot ne változtassuk meg (76H=18, ami a NEWLINE kódja), mert különben a kép széttörik.
A program a következő:
2A0C40
01D602
23
7E
FE76
2802
3680
0B
79
B0
20F3
C9
VISZ
TOV
LD HL,(400C)
LD BC,03D6
INC HL
LD A,(HL)
CP 76
JR Z,TOV
LD (HL),80
DEC BC
LD A,C
OR B
JR NZ,VISZ
RET; D-FILE
; BC-be képhossz +0100
; következő helyre
; A-ba a kód
; A és 76H összehasonl.
; ha egyenlők, akkor TOV
; a pont kódja
; számláló
; vissza BASIC-be
Talán magyarázatra szorul, miért kellett a DEC BC után a LD A,B és az OR B utasításokat használni! A DEC BC nem állítja a FLAG regisztereket, így a kiegészítő utasítások nélkül a JR NZ,XX utasítás nem működne helyesen. Azzal viszont, hogy az A-ba áttett C és B között logikai "VAGY" kapcsolatot létesítünk, egyszerűen meggyőződhetünk arról, hogy mindkét regiszter nulla-e vagy sem. Most nézzük, hogyan oldja meg a képernyő teleírását a következő változat:
2A0C40
01D702
3E76
EDA1
E0
28FB
2B
3680
23
18F5
ELO
LD HL,(400C)
LD BC,02D7
LD A,76
CPI
RET PO
JR Z,ELO
DEC HL
LD (HL),80
INC HL
JR ELO; D-FILE
; D tart. hossza
; NEWLINE kódja
; A-(HL), INC HL, DEC BC
; BASIC-be, ha PO
; a pont kódja
Ebben a verzióban a CPI utasítás elvégzi a feladatok jelentős részét: összehasonlítja a (HL) tartalmát A-val, növeli (HL)-t eggyel, és levon BC-ből egyet. A jelzőbiteket a következő módon állítja:
Érdekes lehet még, hogy miért kell a DEC HL, ill. az INC HL utasítás. Ezekre azért van szükség, mert a CPI után a HL már nem az összehasonlított byte-ra, hanem már a következőre mutat, azaz előbb összehasonlít, utána növeli HL-t. A DEC HL utasítás közömbösíti a CPI INC HL utasítást, az INC HL pedig a kód beírása után visszaállítja az eredeti rendet.
A CPI utasítás jól használható két byte-os számlálónak is, különösen akkor, ha a HL növelésére is szükség van. Erre példa a következő program.
Sokszor előfordul, hogy adott karaktereket, pl. egy játékban a veszélyes helyeket vagy bizonyos információkat, meg kell különböztetni a környezettől. Ilyen célokat szolgál a "Villogtató" program, ami átvizsgálja a képernyőt, és adott karaktereket, jelen esetben a "*"-okat, inverzzé alakítja, és viszont. Ha periodikusan újraindítjuk a programot, akkor a képen levő összes csillag villogni látszik. Ha a csillag helyett más karaktert akarunk kiemelni, csak a CP utasításban levő számot kell megváltoztatnunk a megfelelő kód hexadecimális alakjára.
2A0C40
01FF02
EDA1
E0
7E
E67F
FE17
20F6
7E
C680
77
18F0
UJATLD HL,(400C)
LD BC,02D6
CPI
RET PO
LD A,(HL)
AND 7F
CP 17
JR NZ,UJAT
LD A,(HL)
ADD A,80
LD (HL),A
JR UJAT; D-FILE
; D-tartomány hossza
; BC=BC-1, HL=HL+1
; BC=0 esetén BASIC-be
; 7-es bit bullázása
; A-ban "*" kódja van?
; ha nem, vissza
; inverzzé alakítás
; inverz beírása
; vissza
A villogást előidéző BASIC program:
1000 FOR A=1 TO 704
1010 PRINT "*";
1020 RAND USR 15614
1030 NEXT A
1060 RAND USR 16516
1070 G0T0 1060
A program működése a következő:
A következő program az animáció lehetőségeit mutatja be. A program egy pontot mozgat a képernyőn balra vagy jobbra aszerint, hogy az "A" vagy az "L" gombot nyomtuk meg.
180D
0000
0000
2A0C40
23
3680
228840
010A00
3A8440
FE26
2805
FE31
2812
C9
2A8840
2B
3E76
BE
C8
3680
228840
23
3600
C9
2A8840
23
3E76
BE
C8
3680
228840
2B
3600
C9
ELO
BAL
JOBBJR ELO
0000
0000
LD HL,(400C)
INC HL
LD (HL),80
LD (4088),HL
LD BC,000A
LD A,(4086)
CP 26
JR Z,BAL
CP 31
JR Z,JOBB
RET
LD HL,(4088)
DEC HL
LD A,76
CP (HL)
RET Z
LD (HL),80
LD (4088),HL
INC HL
LD (HL),0
RET
LD HL,(4088)
INC HL
LD A,76
CP (HL)
RET Z
LD (HL),80
LD (4088),HL
DEC HL
LD (HL),0
RET; kódvizsgálatra
; gomb kódja (adat)
; a pont helye (adat)
; D-FILE
; első karakter
; pont az első helyre
; hely beírása
; BC-be a GOTO sorszáma
; A-ba a gomb kódja
; 26H = CODE "A"
; 31H = CODE "L"
; ha egyik sem
; pont helye
; NEWLINE kódja
; BASIC-be, ha Z
; új pont beírása
; új pont helye
; előző pont kioltása
; BASIC
; pont helye
; NEWLINE kódja
; BASIC, ha Z
; új pont
; új pont helye
; előző pont kioltása
; BASIC
A BASIC rész:
8 RAND USR 16522
10 POKE 16518,CODE INKEY$
20 GOTO USR 16516
A programot a BASIC rész indításával kell futtatni. A nyolcas sor elindítja a gépi programot a LD HL,(4000) sortól kezdődően. Ekkor az kitesz egy pontot a bal felső sarokba, majd visszatér BASIC-be. A tizedik sor a 16518-as (4086H) byte-ba POKE-olja az éppen megérintett gomb kódját. A húszas GOTO USR 16516 újra elindítja a gépi kódú programot, most az elejétől úgy, hogy visszatéréskor a BC-ben megadott sorra ugrik (a USR által felvett érték a BC tartalma).
A 16516-ról indított program a következőket teszi:
A következő programmal a hangjelzés lehetőségére adunk egy példát.
1606
D3FF
10FE
DBFE
41
10FE
0C
41
20F3
15
20F0
C9
HANG
VAR
MAGALD D,06
OUT (FF),A
DJNZ VAR
IN A,(FE)
LD B,C
DJNZ MAGA
INC C
LD B,C
JR NZ,HANG
DEC D
JR NZ,HANG
RET; számláló
; PORT be
; bekapcsolás ideje
; PORT ki
; kikapcsolás ideje
; periódusidő
; növelés
; ciklusszámláló
; BASIC
A program csak FAST üzemmódban működik!
A szirénaprogram a ZX81 MIC. kimenetének ki-be-kapcsolgatásával szirénahangot ad, ha az említett kimenet jelét felerősítjük. Erre a célra egy dinamikus lemezjátszó-bemenettel rendelkező erősítő a legjobb, de a tv-készülék kismértékű elhangolásával szintén hallható a sziréna hangja.
A program az OUT (FF),A utasítással kapcsolja be és az IN A,(FE) utasítással kapcsolja ki a MIC. kimenetet. A két utasítás közötti idő határozza meg a hang frekvenciáját. Az időtartamokat jelen esetben a DJNZ ciklusok szabályozzák, a B regiszter tartalmának megfelelően. A változó frekvenciájú hangot azzal érjük el, hogy a B regisztert minden periódus után csökkentjük eggyel. A D regiszter a szirénahang hosszúságát szabályozza, így ha hosszabb hangot kívánunk, nagyobb számot kell tenni. A program SLOW módban nem működik, mert ilyenkor a ZX81 nem tud a perifériákkal foglalkozni.
A következő példa szintén "hangos"; a "Géptávíró" program hallható morzejelekké alakítja a beütött karaktereket.
181D
0000
0660
3E7F
D3FF
10FA
DBFE
0660
10FE
0D
20EF
21FF26
2B
7D
B4
20FB
1818
CD230F
3A8640
061A
90
21C940
1600
5F
19
5E
1609
15
CB23
30FB
15
C8
CB23
3004
0EFF
18C3
0E50
18BF
7355
3F2F
2723
2120
3038
3C3E
0518
1A0C
0212
0E10
0417
0D14
0706
0F16
1D0A
0803
0911
0B19
1B1C
HANG
BE
KI
SZU
SC
TOL
MEG
ROV
TABJR SC
0000
LD B,60
LD A,7F
OUT (FF),A
DJNZ BE
IN A,(FE)
LD B,60
DJNZ KI
DEC C
JR NZ,HANG
LD HL,26FF
DEC HL
LD A,L
OR H
JR NZ,SZU
JR MEG
CALL 0F23
LD A,(4086)
LD B,1A
SUB B
LD HL,TAB
LD D,0
LD E,A
ADD HL,DE
LD E,(HL)
LD D,9
DEC D
SLA E
JR NC,TOL
DEC D
RET Z
SLA E
JR NC,ROV
LD C,FF
JR HANG
LD C,50
JR HANG
5573
2F3F
2327
2021
3830
3E3C
1805
0C1A
1202
100E
1704
140D
0607
160F
0A1D
0308
1109
190B
1C1B; a billentyű kódhoz
; az adat byte
; be idő
; PORT be
; PORT ki
; a hang hossza
; szünet
; letelt?
; morzekód vizsgálatra
; FAST
; a gomb kódja
; a táblázat címe
; az adott morzekód
; címe
; bitek száma + 1
; balra tol
; ha CY=0, vissza
; ha minden kitolva,
; akkor BASIC
; balra tol
; ha CY=0, akkor rövid
; hosszú idő
; rövid idő
; . ,
; 1 0
; 3 2
; 5 4
; 7 6
; 9 8
; B A
; D C
; F E
; H G
; J I
; L K
; N M
; P O
; R Q
; T S
; V U
; X W
; Z Y
A program BASIC része:
8 POKE 18518,CODE INKEY$
10 RAND USR 16516
40 IF INKEY$="" THEN GOTO 40
50 GOTO 8
Mivel a program használja a ROM-ban található szubrutinokat, mielőtt leírását elolvassuk, tanulmányozzuk át a 4. függelékben közölt ROM térképet! A program működése röviden a következő:
Itt álljunk meg egy pillanatra! Az ötös pontban eljutottunk a morzejelet tároló byte E regiszterbe írásáig, de arról még nem volt szó, milyen formában tároltuk a jeleket! Íme a táblázat:
Morzejel bináris hexadecimális , = --..-- 01110011 73H . = .-.-.- 01010101 55H 0 = ----. 00111111 3FH 1 = .---- 00101111 2FH 2 = ..--- 00100111 27H 3 = ...-- 00100011 23H 4 = ....- 00100001 21H 5 = ..... 00100000 20H 6 = -.... 00110000 30H 7 = --... 00111000 38H 8 = ---.. 00111100 3CH 9 = ----. 00111110 3EH A = .- 00000101 05H B = -... 00011000 18H C = -.-. 00011010 2AH D = -.. 00001100 0CH E = . 00000010 02H F = ..-. 00010010 12H G = --. 00001110 0EH H = .... 00010000 10H I = .. 00000100 04H J = .--- 00010111 17H K = -.- 00001101 0DH L = .-.. 00010100 14H M = -- 00000111 07H N = -. 00000110 06H O = --- 00001111 0FH P = .--. 00010110 16H Q = --.- 00011101 1DH R = .-. 00001010 0AH S = ... 00001000 08H T = - 00000011 03H U = ..- 00001001 09H V = ...- 00010001 11H W = .-- 00001011 0BH X = ..- 00011001 19H Y = -.-- 00011011 1BH Z = --.. 00011100 1CH
A bináris kódban egyesek és nullák formájában szerepelnek a morzejelek, jobbra igazítva. Tehát az utolsó pont, ill. vonás mindig a 0. bithelyre kerül. Ezen kívül az első hasznos bit előtti helyre egyest tettünk. Ez a startbit jelzi majd a hangjelek hosszúságát beállító programrésznek, hogy hol kezdődik a hasznos információ. Szükség is van erre, hiszen a morzekódolás betűnként más-más számú jelet alkalmaz. A hangjeleket meghatározó program először jobbról indulva megvizsgálja a biteket, és az első egyes után következő értékeket már a hangok hosszának beállítására használja. Nézzük tovább a program lépéseit:
A következő program a függőleges mozgatást mutatja be, a ZX81-en gyakorlatilag elérhető legnagyobb sebességen.
1802
0000
2A0C40
114C01
19
228640
0604
3E80
77
112100
19
10F7
CDBB02
2C
28FA
2D
44
4D
CDBD07
3E01
93
2B0B
3E03
93
282D
3E0A
93
C8
18E4
2A0C40
23
23
23
23
ED5B8640
A7
ED52
F29D40
2A8640
112100
A7
ED52
228640
3680
118400
19
3600
18BD
ED5B0C40
219002
19
ED5B8640
A7
ED52
FA9D40
2A8640
3600
1121
19
228640
116300
19
3680
1897
HELY
ELSO
SC
FEL
LEJR KEZD
0000
LD HL,(400C)
LD DE,014C
ADD HL,DE
LD (HELY),HL
LD B,4
LD A,80
LD (HL),A
LD DE,21
ADD HL,DE
DJNZ ELSO
CALL 02BB
INC L
JR Z,SC
DEC L
LD B,H
LD C,L
CALL 07BD
LD A,1
SUB E
JR Z,FEL
LD A,3
SUB E
JR Z,LE
LD A,0A
SUB E
RET Z
JR SC
LD HL,(400C)
INC HL
INC HL
INC HL
INC HL
LD DE,(HELY)
AND A
SBC HL,DE
JP P,SC
LD HL,(HELY)
LD DE,21
AND A
SBC HL,DE
LD (HELY),HL
LD (HL),80
LD DE,84
ADD HL,DE
LD (HL),0
JR SC
LD DE,(400C)
LD HL,0290
ADD HL,DE
LD DE,(HELY)
AND A
SBC HL,DE
JP M,SC
LD HL,(HELY)
LD (HL),0
LD DE,21
ADD HL,DE
LD (HELY),HL
LD DE,63
ADD HL,DE
LD (HL),80
JR SC; adatátugrás
; D-FILE
; kezdeti hely
; ütőnagyság = 4 pont
; pontot tesz
; 21H egy sor hossza
; alatti hely
; következő
; nyomógombvizsgálat
; HL = FFFF ?
; visszaállít
; dekóder
; "Z" ?
; "C" ?
; "Q" ?
; vissza BASIC-be
; D-FILE
; a legfelső pont helye
; flag-törlés
; fent van
; gombteszt
; 21H = sorhossz
; flag-törlés
; új hely
; új pont
; alsó pont távolsága
; 4 * 21
; alsó pont kioltva
; gombteszt
; legalsó sor
; flag-nullázás SBC
; miatt
; lent van?
; felső pont helye
; pont kioltva
; új hely
; alsó pont kigyújtva
; gomteszt
A program a nagysebességű mozgatást azzal éri el, hogy a billentyűk vizsgálatát közvetlenül a billentyűzet-dekódoló szubrutinok hívásával végzi, így BASIC rész sehol sem lassítja a programot. A program működését nem kell részletezni, mert az eddigi példák alapján a legtöbb rész könnyen érthető; csak az új, elemet tartalmazó részek magyarázatára szorítkozunk.
A legfontosabb újdonság az SC címkénél kezdődő billentyűzetvizsgáló, ill. dekódoló programrész. Ez a CALL 02BB utasítással hívott billentyűvizsgáló szubrutinnal kezdődik (l. ROM térkép). Az említett rutin végigfut a billentyűzeten, és a gombok állapotát a HL regiszterbe töltött speciális kóddal jelzi. Erről a kódról elég annyit tudni, hogy ha értéke FFFFH, akkor a szubrutin nem talált aktív gombot. Ezt a tényt a program ki is használja, mert mindaddig, amíg a kód eggyel való növelése nullát ad, újrakezdi a vizsgálatot.
Ha a vizsgáló rutin nem FFFFH-t ad vissza, akkor lép működésbe a dekódoló szubrutin. Ez 1 és 76 közötti számokat állit elő az előbbi két byte-os értékekből, amiket a BC regiszterbe kell a rutin hívása előtt tölteni. A dekódolt értéket a DE regiszterből olvashatjuk ki visszatérés után. A DE-ben levő szám még nem a karakter kódja, hanem a karakter billentyűjének a klaviatúrán elfoglalt helyét mutatja, ezért olyan ismeretlenek a vezérlőkaraktereket kiszűrő utasításokban szereplő számok. Az egyes karaktereknek megfelelő értékeket az 5. függelék mutatja.
Magyarázatra szorul az AND A utasítás a SBC HL,DE utasítások előtt. Erre a C FLAG nullázása miatt van szükség, mert a SBC utasítás a CY értékét is kivonja (az AND A utasítás C-be nullát ír).
Nem használtuk még korábban a JP utasítást sem. A JP NN az NN byte-ra ugratja a programot, a JR utasítás relatív módszerével ellentétben függetlenül a program, utasítás helyétől. Használatára eddig nem volt szükség, mert a JR minden feladatra alkalmas volt. A JP utasítást csak ott érdemes használni, ahol a JR lehetőségei nem elegendők, pl. ha 128 byte-nál nagyobb távolságot kell átugrani, vagy ha olyan feltétel szerint kell elágaztatni a programot, amilyenre nincs JR utasítás. A JR relatív ugrás általában előnyösebb, hiszen a JP-t nem tartalmazó programot újrafordítás nélkül lehet más címre áthelyezni.
A következő példa a képernyő elraktározásához ad segítséget.
1832
181B
2A1440
221640
36EA
110003
19
221A40
2A1440
23
3676
010400
0DE006
210400
CDB09
23
23
23
23
23
EB
2A0C40
01D702
EDB0
C9
210400
CDD809
23
23
23
23
23
ED5B0C40
01D702
EDB0
C9
KET
HAR
JR HAT
JR KET
LD HL,(4014)
LD (4016),HL
LD (HL),EA
LD DE,0300
ADD HL,DE
LD (401A),HL
LD HL,(4014)
INC HL
LD (HL),76
LD BC,0004
CALL 06E0
LD HL,4
CALL 09D8
INC HL
INC HL
INC HL
INC HL
INC HL
EX DE,HL
LD HL,(400C)
LD BC,02D7
LDIR
RET
LD HL,4
CALL 09D8
INC HL
INC HL
INC HL
INC HL
INC HL
LD DE,(400C)
LD BC,02D7
LDIR
RET
; E-LINE
; CH-ADD
; REM kódja
; STKBOT
; E-LINE
; leendő sorszám
; NEWLINE rutin
; keresett sor száma
; sorcímkeresés
; HL-ben a sor címe
; D-DILE
; sorszám
; sorcímkeresés
; D-FILE
A BASIC rész:
10 RAND USR 16520
13 FOR A=1 TO 704
18 PRINT "*";
30 NEXT A
40 RAND USR 16518
50 STOP
20 RAND USR 16516
A képraktározó program egy REM utasításban tárolja a képernyőn levő karakterek kódjait, így a tárolt képek egy pillanat alatt kirajzolhatók, sőt magnón rögzíthetők. Ha a bemutató BASIC programot elindítjuk, akkor az a 10-es sorával lefuttatja a gépi program első részét, ami a 4-es sorba ír egy 768 byte-os REM utasítást. A REM utasítás beírása után a BASIC programot újra kell indítani a 13-as sortól. Az ott következő rész a tárolandó képet rajzolja ki, majd a második gépi nyelvű részt futtatva a REM szövegmezőjébe tölti a képet. Ezután, ha elő akarjuk hívni a csillagokat, akkor elég a 20-as sort indítani, és már meg is jelent a kép.
A program, mint láttuk, három különböző részből áll. Az első rész (16520-as cím) a 06E0 című NEWLINE szubrutin segítségével egy 768 byte-os REM sort tesz a BASIC programba (ez a szubrutin működik, amikor egy új sort akarunk beilleszteni). A NEWLINE rutin az E-LINE után levő munkaterületet a CH-ADD-ban tárolt címtől a terület végét jelző STKBOT-ig teljes egészében a beillesztendő sor szövegének tekinti. Ezt tudva az E-LINE után következő első byte-ba a REM utasítás kódját tesszük, a munkaterület végét jelző STKBOT-ot pedig az E-LINE-hoz képest 768-cal eltoljuk. Az STKBOT eltolása nem okoz különösebb problémát a ZX81 rendszerében, mert az ott kezdődő kalkulátor-stack csak a számításoknál szükséges. A REM sorszámát a BC regiszter tartalma szerint állítja be a szubrutin. Ha több képet kívánunk tárolni, akkor a BC minden újabb REM sor után megváltoztatandó.
A második rész (KET) a kép REM-be másolását végzi. Itt először a sorcímkereső szubrutin segítségével meghatározzuk a tárolásra előkészített BASIC REM sor kezdőcímét. A rutin a HL számú sort keresi meg, és a megtalált címet a HL-be teszi vissza. Ezután a sor szövegmezőjébe másoljuk a képmemóriát az LDIR blokkmásoló utasítással. Az LDIR a HL címről a DE címre másol át BC számú egymás után következő byte-ot.
A harmadik rész a képmemóriába tölti a REM-ben tárolt képet. Teljesen ugyanúgy működik, mint a második rész, csak a másolás forrása és célja van felcserélve.
Szintetizátor:
CDE702
41
3EAA
D3FD
10FA
CD460F
D0
41
10FE
CDBB02
2C
28FA
2D
44
4D
CDBD07
21AA40
19
2B
7E
4F
18DD
FFDF
BFAF
00EF
CF00
A400
0201
0403
0605
0807
0A09
0C0B
0E0D
100F
1211
1413
6900
8191
006F
7989
9900
ELO
HANG
KOZ
SC
TABCALL 02E7
LD B,C
LD A,AA
OUT FD,A
DJNZ HANG
CALL 0F46
RET NC
LD B,C
DJNZ KOZ
CALL 02BB
INC L
JR Z,SC
DEC L
LD B,H
LD C,L
CALL 07BD
LD HL,TAB
ADD HL,DE
DEC HL
LD A,(HL)
LD C,A
JR ELO
DFFF
AFBF
EF00
00CF
00A4
0102
0304
0506
0708
090A
0B0C
0D0E
0F10
1112
1314
0069
9181
6F00
8979
0099; FAST
; hang idő
; hang ki
; BREAK teszt
; ha BREAK akkor
; BASIC-be
; csnd
; nyomógomb vizsgálat
; van nyomott gomb?
; érték visszaállítása
; dekóder
; táblázat címe
; DE-ben a gomb kódja
; a hangot meghatározó
; érték A-ban
; vissza a hangadáshoz
; re do
; fa mi
; di xx
; xx ri
;xx fi
; xx di2
; si li
; do2 xx
; la ti
; xx so
Ez a program kis szintetizátort csinál a ZX81-ből. Ha a billentyűzet alsó két sorát zongorabillentyűknek tekintjük, egy oktáv áll a rendelkezésinkre. A hangok nem a legtisztábbak, de ha valakinek kedve van hozzá, nyugodtan finomíthat a hangzáson a kódtábla értékeinek változtatásával. A program nem tartalmaz lényegesen új elemeket, úgyhogy a működés magyarázatától eltekintünk.
Függelék
A 67-111, 122-125 kódokhoz nem tartozik karakter,
A 0 kódhoz a szóköz tartozik, a 112-127 kódokhoz vezérlőkarakterek tartoznak az alábbiak szerint:
kurzor fel 112 kurzor le 113 kurzor balra 114 kurzor jobbra 115 GRAPHICS 116 EDIT 117 NEWLINE 118 RUBOUT 119 MODE 120 FUNCTION 121 KONSTANS 126 CURSOR 127
0 A program hibátlanul lefutott. 1 A NEXT után szereplő ciklusváltozó nem létezik. 2 Az utasításban szereplő változót még nem definiáltuk. 3 Az utasításban szereplő tömbváltozó egyik dimenziója nagyobb a DIM utasításban megadottnál. 4 A memória megtelt. 5 A képernyő betelt. CONT utasítás hatására a képernyő törlődik, a program futása folytatódik. 6 Az utasításban szereplő kifejezés értéke nagyobb mint 1.62*10^38. 7 RETURN utasítás GOSUB nélkül. 8 INPUT utasítást közvetlen utasításként próbáltunk adni, ez nem megengedett. 9 STOP utasítás. A Az utasításban szereplő függvény argumentuma kívül esik az értelmezési tartományon. B Az utasításban szereplő egész szám kívül esik a megengedett intervallumon (pl. negatív index). C VAL függvény alkalmazása olyan stringre, amely nem jelent számot. D A programot BREAK-kel megszakítottuk. E A SAVE utasításban megadott név üres string.
4. A ZX81 ROM-jának fontosabb szubrutinjai
0000 Itt indul bekapcsolás után a rendszer. Ha ide ugratunk ugyanaz történik, mintha egy pillanatra kikapcsolnánk a tápfeszültséget. 0008 A hibajelző rutin belépési pontja. 010 Karakternyomtató rutin. Az akkumulátorba töltött kódnak megfelelő karaktert a soron következő pozícióba nyomtatja. 007E-00CB Alapkarakterek kódtáblázata. 00CC-00F2 A FUNCTION mód szimbólumainak kódoló táblázata. 00F3-0100 A GRAPHICS mód kádtáblája. 0111-01FB A parancsok kódtáblázata, minden szó utolsó karaktere invertálva szerepel. 02BB A billentyűvizsgáló szubrutin. A szubrutin a visszatérés után a HL regiszterbe rakja a lenyomott billentyű speciális kódját (FFFFH ha nincs aktív gomb). Ez nem a jól ismert kód, azt a 07BD szubrutin segítségével kaphatjuk.03C3 NEW rutin. 06E0 NEWLINE programíráskor. Az éppen irt sort beszerkeszti a programba, a BC-be írt sorszámnak megfelelő helyre. Végrehajtás után a BASIC-be tér vissza.07BD Billentyűzetdekódoló szubrutin. A BC-be töltött speciális kétbyte-os kódok ennek segítségével alakíthatók az ismert kódokká. A rutin a DE regiszterbe 1-76-ig terjedő számot rak, ami a gombnak a billentyűzeten elfoglalt helyére utal. A HL regiszter megadja azt a címet, ahol a karakter "igazi" kódja van. ezeket a címeket, valamint a billentyű helyére utaló kódokat az 5. függelék tartalmazza.08F5 PRINT AT rutin. A BC (B=Y, C=X) koordinátákra teszi a PRINT pozíciót. 09D8 Sorcímkereső rutin. A keresett sor számát HL-be kell tölteni, a rutin hívása után ugyancsak a HL tartalmazza a sor kezdőcímét. 0A2A CLS rutin. 0BB2 PLOT-UNPLOT rutin. A BC (B=Y, C=X) helyre tesz pontot, illetve kioltja azt, a T-ADDR rendszerváltozó (16432-33) értékétől függően. T-ADDR=0C98 esetén PLOT, T-ADDR 0C9E esetén UNPLOT utasítás végrehajtása következik.0C0E SCROLL rutin. 0F23 FAST rutin. 0F2B SLOW rutin. 0F46 BREAK rutin. A BREAK gomb érintése esetén a C jelzőbitet nullázza, így NC feltétel teljesül. 1E00 Karaktergenerátor kezdete.
Karakter | A gomb helyére utaló kód |
A kódot tartalmazó memóriarekesz címe |
||
HEX |
DEC |
HEX |
DEC |
|
Z | 1 |
1 |
7E |
126 |
X | 2 |
2 |
7F |
127 |
C | 3 |
3 |
80 |
128 |
V | 4 |
4 |
81 |
129 |
A | 5 |
5 |
82 |
130 |
S | 6 |
6 |
83 |
131 |
D | 7 |
7 |
84 |
132 |
F | 8 |
8 |
85 |
133 |
G | 9 |
9 |
86 |
134 |
Q | A |
10 |
87 |
135 |
W | B |
11 |
88 |
136 |
E | C |
12 |
89 |
137 |
R | D |
13 |
8A |
138 |
T | E |
14 |
8B |
139 |
1 | F |
15 |
8C |
140 |
2 | 10 |
16 |
8D |
141 |
3 | 11 |
17 |
8E |
142 |
4 | 12 |
18 |
8F |
143 |
5 | 13 |
19 |
90 |
144 |
0 | 14 |
20 |
91 |
145 |
9 | 15 |
21 |
92 |
146 |
8 | 16 |
22 |
93 |
147 |
7 | 17 |
23 |
94 |
148 |
6 | 18 |
24 |
95 |
149 |
P | 19 |
25 |
96 |
150 |
O | 1A |
26 |
97 |
151 |
I | 1B |
27 |
98 |
152 |
U | 1C |
28 |
99 |
153 |
Y | 1D |
29 |
9A |
154 |
NEWLINE | 1E |
30 |
9B |
155 |
L | 1F |
31 |
9C |
156 |
K | 20 |
32 |
9D |
157 |
J | 21 |
33 |
9E |
158 |
H | 22 |
34 |
9F |
159 |
SPACE | 23 |
35 |
A0 |
160 |
. | 24 |
36 |
A1 |
161 |
M | 25 |
37 |
A2 |
162 |
N | 26 |
38 |
A3 |
163 |
B | 27 |
39 |
A4 |
164 |
. | 28 |
40 |
A5 |
165 |
, | 29 |
41 |
A6 |
166 |
? | 30 |
42 |
A7 |
167 |
/ | 31 |
43 |
A8 |
168 |