Extensions
Extensions
Extensions

Die Sache mit dem Speicherplatz für Zusatzfunktionen bei einer fertiggestellten Software ist immer ein Problem. Entweder hält man als Entwickler von vornherein einen kleinen Bereich für solche Vorhaben vor oder man schränkt im Bedarfsfall den Hauptspeicher ein. Beides ist mit TSB möglich. Die TSB-Erweiterungen VDCBasic, HSG und TSB.MON verkleinern den Hauptspeicher, und die Erweiterungen DirSelect und MAP nutzen zweitweise unbenutzte Speicherräume.

Mit X! wollen wir einen für TSB neuartigen Mechanismus einführen, der gezielt das Einbinden von zusätzlichen, nur zeit- und anwendungsweise erforderlichen Befehlen ermöglicht. Bereits MAP gehört in diese Kategorie. Auch dieser Befehl ist kein permanenter Teil von TSB, sondern er wird - da er für das Erstellen und Kontrollieren von Spielwelten gedacht ist - eben nur für die Entwicklung und Ausführung von Spielen installiert.

Der Extensionbefehl X! wird vom Interpreter über den Vektor $897c via RTS aufgerufen (Zieladresse minus 1). Die Routine muss mit einem Einsprung in die Interpreterschleife enden (nicht mit RTS!), am besten mit JMP $9567 (wenn der Befehl Parameter ausgewertet hat) oder mit JMP $9564 (wenn es keine gab).

Um genügend Spielraum zu haben, kommt nun X! dazu, ein Befehl, der so angelegt ist, dass seine Ausführung durch weitere Schlüsselwortzeichen aufgeteilt werden kann, um so mehrere unterschiedliche Aufgaben zu erledigen. Die Hauptroutine von X! (von der aus in unterschiedliche Unterbefehle verzweigt werden soll) liegt nach der Installation an irgendeiner genügend großen freien Speicherposition, z.B. $CA00 (bei X!INST; ein Bereich von 256 Bytes, der seit TSBneo genau hierfür freigehalten wird), $CB00 (bei MAP; auch hier liegen normalerweise 256 freie Bytes) oder $C000 (bei X!CLR und X!STEP; wenn der Hires-Modus nicht verwendet wird, sind hier 1016 Bytes frei). Im MEM-Modus ist auch der Standard-Textscreen an Position $0400 unbenutzt und frei für Programme. Wurde der neue Befehl erfolgreich nachgeladen (ein Beispiel für eine solche Laderoutine siehe weiter unten bei X!INST), muss man nur den Befehlsvektor für X! ($897C) auf den Ort des Befehls im Speicher legen (Adresse minus 1, da die Routine per RTS über den Stack angesprungen wird) und es kann losgehen.

Zur Unterscheidung der Aufgaben von X! wird der Befehl also durch ein zweites Schlüsselwort erweitert, das dessen Tätigkeitsfeld näher angeben soll. Dieses zweite Schlüsselwort kann aus dem Pool der vorhandenen TSB- und Basic-V2-Schlüsselwörter genommen werden, ähnlich wie das bei den Memory-Befehlen gemacht wurde (das TSB-Schlüsselwort MEM wird dort mit den V2-Schlüsselwörtern DEF, READ, OR, CONT, SAVE, LOAD, PEEK, CLR, POS und RESTORE kombiniert, um die jeweilige Aufgabe des MEM-Befehls festzulegen). Natürlich kann man aber auch ein (fast) beliebiges anderes Zeichen als Unterscheidungsmerkmal einsetzen, Buchstaben oder Ziffern, sogar Satzzeichen (X!a, X!1, X!! usw.). Im folgenden Beispiel, das direkt nach der Erkennung des Befehls X! durch den TSB-Interpreter einsetzt, folgt auf das Label "determine" eine entsprechende Erkennungsroutine für die Token von CLR und STEP (für die Befehle X!CLR und X!STEP, s. weiter unten):

determine:
        jsr chrget        ; get next byte (after token of X!)
        cmp #$9c          ; is token of CLR?
        beq befxclr       ; yes, process command X!CLR
        cmp #$a9          ; or token of STEP?
        bne synterr       ; no: syntax error
        jmp befxstep      ; yes, process command X!STEP

Auch neue Funktionen können ab TSB-Version 2.40420 eingefügt werden. Genau wie die Befehle müssen sie in den TSB-Schlüsselwort-Erkennungsapparat eingebunden werden. Die Patch-Adresse hierfür lautet $952b. Diese Adresse ist Teil der Erkennungskette für "arithmetische" Ausdrücke (wozu auch String-Ausdrücke gehören). Sie wird an dieser Stelle aufgetrennt für die geplante Erweiterung, und zwar wird hier ein Sprung auf die neue Funktion eingefügt, z.B. JMP $CA00. Innerhalb dieser neuen Funktion muss es dann im Fehlerfall (hinter X! steht ein falscher Bezeichner) einen Rücksprung zur Auswertungskette geben, damit diese nicht unterbrochen wird: JMP $9535. Und um die Funktion USE durch den Patch nicht auszuschließen, muss der Code mit der Befehlssequenz

bne +            ; Test auf USE
jmp $952e        ; Funktion USE erkannt, dort weiter
+ cpy #$b2       ; Funktion X!?
bne ++           ; nein, zurück in die Auswertungskette
beginnen (bei "++" ist der Rücksprung in die Kette zu $9535). Wenn aber alles in Ordnung ist, endet die Funktion mit RTS als Rücksprung.

Zur Veranschaulichung gibt es die beiden Funktionen X!FCHR() zum Auslesen des Inhalts des Bildschirmspeichers an einer bestimmten Position und entsprechend X!FCOL() zum Auslesen der Farbinformation an dieser Stelle.

Funktionen (Beispiel "FNSCREEN") einbinden mit:

100 if a=0 then a=1: load"ext.fnscreen",use,0,$ca00
110 if d!peek($ca00)=$03d0 then poke $952b,$4c: d!poke $952c,$ca00

Und nun die...

<nach oben>


Beispiele

Um einer eventuellen Zurückhaltung beim Programmieren von Extensions zu TSB entgegenzuwirken, hier ein paar Beispiele, was man mit diesem Mechanismus erreichen kann und wie man ihn implementiert. Wir stellen hier drei neue Befehle und zwei neue Funktionen vor: die Befehle X!INST, X!CLR, X!STEP, die Funktion X!MAP und die Funktionen X!FCHR und X!FCOL.


Der Befehl X!INST

Der Befehl x!inst dient dazu, den Inhalt von Strings zu manipulieren, ohne dabei den sonst üblichen Stringmüll entstehen zu lassen, der irgendwann zu einer lästigen Garbage-Collection-Unterbrechung des laufenden Programms führen würde.

Besonders bei Spielen wäre eine solche Unterbrechung - egal, wie kurz sie auch dauern möge - einfach tödlich für den Spielspaß. Die bereits in TSB implementierte Funktion INST ist daher für Spiele unbrauchbar, denn sie legt bei jedem Aufruf die in den Argumenten angegebenen Strings neu an, was schnell den freien Basic-Speicher mit zurückgebliebenen "Altlasten" füllt. Mit x!inst passiert das nicht.

Syntax:

x!inst <Einfügestring>, <zu manipulierender String>, <Einfügeposition>

Dabei können alle Strings durch Variablen, auch Array-Variablen, repräsentiert werden. Der Einfügestring (Parameter 1) darf auch literal sein (eine Stringkonstante in Anführungszeichen; beim zu manipulierenden String macht das keinen Sinn, würde aber nicht zu einer Fehlermeldung führen). Der Einfügestring kann aus mehreren Zeichen bestehen, darf aber nicht die Gesamtlänge des zu manipulierenden Strings (Parameter 2) überschreiten. Genau wie bei der Funktion INST zählt die Einfügeposition (Parameter 3) ab 1. Der Wert 0 liegt außerhalb des Strings.

Wenn als Parameter 2 ein nicht vorhandener String angegeben wird, wird dies ignoriert. x!inst legt weder einen neuen String an, noch gibt es eine Fehlermeldung.

Mögliche Fehlermeldungen sind jedoch:

ERRORUrsache
- SYNTAX der Name des Befehls wurde falsch geschrieben (z.B. "x!insert") oder es fehlen Parameter
- TYPE MISMATCH dort, wo Strings stehen müssen (Parameter 1 und 2), wurde eine Zahl oder Zahlenvariable angegeben, umgekehrt bei Parameter 3
- ILLEGAL QUANTITY der dritte Parameter zeigt außerhalb des zu manipulierenden Strings (ist 0 oder negativ bzw. größer als die Stringlänge)
- STRING TOO LONG der Einsetzstring (Parameter 1) passt an der gewünschten Position nicht mehr in den Zielstring (Parameter 2)

Hinweis: Da der Befehl den String direkt manipuliert, werden alle Strings, die literal innerhalb des Programms angelegt wurden, auch dort verändert. Das heißt, dass sich in diesem Fall durch die Anwendung von x!inst der Programm-Code ändert. Um das zu verhindern, sollte man den zu behandelnden String bereits bei der Definition auf den String-Heap kopieren (a$=""+a$). Wenn die ursprüngliche Stringdefinition im Verlauf des Programms jedoch nicht mehr gebraucht wird, kann man diese Eigenschaft von x!inst auch ignorieren (nicht aber in der Programmentwicklungsphase!)

 

x!inst von einem Programm aus einbinden und aktivieren

x!inst residiert an Speicherposition $CA00. Dort stehen nach der Installation die Werte 160 und 0. Die folgenden Basic-Zeilen fragen diese Stelle ab und laden bei Bedarf den neuen Befehl dorthin. Um den Befehl zu aktivieren, ist der POKE nach $897C in Zeile 130 erforderlich (POKE-Wert: Befehlsadresse minus 1). Anders als bei LOAD beginnt das Programm nach dem Laden nicht von vorn (wie nach RUN), die Installationsroutine könnte also an beliebiger Stelle im Programm stehen.

100 cls: centre "x!inst installieren": print at(2,1)"";
110 if d!peek($ca00)<>160 then do
120   scrld def $ca,1,1,3:scrld 1,u,3,"ext.inst": scrld restore
130   if d!peek($ca00)=160 then d!poke $897c,$c9ff: else error
140 done
150 if d!peek($ca00)=160 then print "x!inst ist eingebunden"

Auf der TSB-Diskette befindet sich ein spezielles Installierprogramm für alle vorhandenen Extensions ("extinstaller.tsb").

Zwei weitere X!-Befehle sind X!CLR und X!STEP:

<nach oben>


Die Befehle X!CLR, X!STEP und die Funktion X!MAP

Im Rahmen des Spiele-Kooperationsprojektes "Demon Block" auf der C64-Userplattform "forum64", das der User @Omega (allseits bekannt durch das Spiel Ghost Valley) ins Leben gerufen hat, entstanden für Demon Block zwei Befehle und eine Funktion, die exakt auf die Bedürfnisse dieses Spiels zugeschnitten sind. Sie erleichtern (und beschleunigen extrem) die Handhabung der verwendeten Spielsteine, indem sie langwierige Vorgänge wie Suchvorgänge durchführen, deren Ergebnisse zusammenfassen und unmittelbar zur Weiterverarbeitung zur Verfügung stellen.

Demon Block Screenshot
Bild 1: In der Löschphase: der gelbe Stein.
Gleich fällt der darüber befindliche Glasstein.

X!CLR scannt ein 12×12 Kacheln großes Spielfeld (bestehend aus je 2×2 Zeichen) nach dem Vorhandensein bestimmter "Spielsteine", zählt deren Anzahl und legt das Ergebnis im Kassettenpuffer (an $036a) ab. Gefundene Steine werden dann daraufhin getestet, ob sie direkt neben- oder untereinander liegen. Solche Steinkombinationen registriert der Befehl und liefert ihre Position auf dem Spielfeld in einer von Basic aus zugänglichen Liste ab (ebenfalls im Kassettenpuffer, max. 144 Bytes ab $036b). Die zusammenhängenden Steine können daraufhin im weiteren Verlauf des TSB-Programms mit einer kleinen Animation gelöscht werden.

X!CLR bereitet das Löschen zusammenhängender Steine vor.

X!STEP erkennt alle Spielsteine auf dem genannten Spielfeld, die nach der Anwendung von X!CLR und des nachfolgenden Löschvorgangs "in der Luft hängen", bei denen also kein weiterer Stein direkt unterhalb vorkommt. Da dies nicht sein darf, werden nun alle über der Lücke befindlichen Steine um eine Position nach unten verlagert, und zwar an allen Stellen, wo dies durch X!CLR nötig wurde. Dabei zählt X!STEP die Anzahl der Fallvorgänge. Auch dieser Wert wird an einer Stelle im Kassettenpuffer abgelegt (an $0369) und kann dort vom TSB-Programm zur weiteren Verwendung abgerufen werden.

X!STEP zählt alle Fallvorgänge und beendet eventuelle "Schwebezustände".

X!MAP (eine Funktion) liest das auf dem Bildschirm angezeigte Spielfeld aus und liefert für jede Position die vom Spiel an dieser Stelle eingesetzte Spielsteinnummer zurück: 1 bis 48 oder 0, wenn an der Stelle kein Spielstein eingetragen war (nr=x!map(zl,sp)). Damit ist sie in gewisser Weise eine Umkehrung des Befehls MAP. Zum Einbinden der Funktion müssen zwei POKEs ausgeführt werden: POKE $952b,$4c: D!POKE $952c,Funktionsadresse (z.B. $ca82, s. oben).

Im Code von X!MAP ist eine weitere Funktion integriert: X!FCOL (s. unten) zum Auslesen der Farbe eines Spielsteins.

X!MAP liefert die Nummern der Spielsteine zurück.

Die beiden Befehlsroutinen liegen (in diesem Fall) im ungenutzten Bereich für die Farben eines Hires-Bildes ab $C000. Die Routine der Funktion ist relokatibel und kann an irgendeiner freien Stelle im Speicher abgelegt werden.

 

X!CLR, X!STEP und X!MAP sind absolute Spezialbefehle, die nur im Kontext des Spiels Demon Block einen Sinn machen. Durch den X!-Mechanismus sind in TSB solche Ergänzungskonstruktionen auf einfache Weise möglich. TSB kann damit sehr flexibel auf viele unterschiedliche Anforderungen reagieren und problemlos und schnell angepasst werden.


Die Funktionen X!FCHR() und die X!FCOL()

Diese Funktionen sind die Umkehrfunktionen zu ihren entsprechenden Befehlen FCHR (Zeichen in den Bildschirmspeicher schreiben) und FCOL (Bildschirmbereiche einfärben). Sie umgehen damit das aufwändige Berechnen der Speicherposition einer Zeile-Spalte-Koordinate, wie es beim Befehl PEEK erforderlich ist. In Spielen können diese Funktionen recht nützlich sein.

Mit z=x!fchr(zl,sp) liest man also an der Stelle Zeile/Spalte aus, was dort im Bildschirmspeicher zu finden ist und weist es der Variablen Z zu. Die Funktion liefert den Bildschirm-Code des dort angezeigten Zeichens zurück. Die Funktion stellt sich automatisch auf die aktuelle Position des Bildschirmspeichers, die ja nach MEM anders ist, ein.

Mit f=x!fcol(zl,sp) erhält man den numerischen Code der an der Stelle Zeile/Spalte im Farbspeicher ab $D800 befindlichen Farbe zurück und weist ihn der Variablen F zu. Der erhaltene Wert liegt zwischen 0 und 15 (einschließlich).

Der Code der Funktionen ist relokatibel, so dass er an beliebige Stellen im C64-Speicher geladen werden kann. Er hat einen Umfang von 90 Bytes. Wie der Programm-Code der Funktionen dann eingebunden werden muss, haben wir bereits in der Einleitung mit der Beispieladresse $CA00 beschrieben.

 

Die Routinen finden sich auf der TSB-Disk unter dem Namen "ext.fnscreen" zum Download.

<nach oben>