Tricks
Tricks

Manche Dinge möchte man gerne mit TSB tun, man kann es aber nicht, weil genau dafür eine Funktion oder ein Befehl fehlt. Mit ein bisschen Gehirnschmalz und Manipulation (wofür der C64 einfach ideal ist) lassen sich aber doch einige Wünsche erfüllen.

Hier ein paar Tricks:


Neue Befehle in TSB einbinden (z.B. MAP)

Es gibt in TSB drei Token-Plätze, die es erlauben, neue Befehle mit eigenen Schlüsselwörtern hinzuzufügen. Diese Plätze sind die Token 60 bis 62. Eins davon ist bereits mit dem Schlüsselwort CLS belegt und somit vergeben (Token 60).


Befehl MAP einbinden (unter dem Namen "ext.map" auf Disk):

100 if peek($cb00)=$ad then d!poke$897e,$caff 
110 if d!peek($897e) <> $caff then load"ext.map",u,0,$cb00


bzw. 100 ...then poke$897e,$ff:poke$897f,$ca (vor TSB v2.30803)

Der Trick ist:
Die Token waren deswegen bislang unbelegt, weil sie vom V2-Interpreter als Operatoren missverstanden werden, nämlich als die Zeichen "<" (60, wird vom Interpreter in 179 verwandelt), "=" (61, wird 178) und ">" (62, daraus wird 177). TSB weiß das nun und kann mit der V2-Umwandlung umgehen.

Die nach CLS verbliebenen zwei Positionen 61 und 62 sind daher vergebbar, wobei Token 62 bereits mit dem (änderbaren) Schlüsselwort MAP vorbelegt ist. Solange keine zugehörige Befehlsroutine implementiert wird, kommt beim Aufruf von MAP die Fehlermeldung "not yet active error".

Der Befehlsvektor für MAP liegt an Adresse $897e und muss für die aktuelle MAP-Routine "ext.map" mit der Befehlsadresse $CB00 (minus 1 = $CAFF) beschickt werden. Der Text des Schlüsselworts MAP befindet sich an Adresse $8710 und ist - falls man das Schlüsselwort ändern will - drei Zeichen lang (hier: m-a-P), wobei das letzte Zeichen geSHIFTet (Zeichen-Code OR 128) eingegeben werden muss. Auf der TSB-Diskette befindet sich unter dem Namen "instcmd.tsb" ein Installationsprogramm für solche "Extension"-Befehle.

<nach oben>


Laufende Programme ändern

Die Spiele Sokoban, ByPuzz und ByPixx sind modular aufgebaut: bei Zeile 6000 beginnen dort die Spielfelddaten, die für eine neue Runde jeweils ausgetauscht werden können. Allerdings hier noch "von Hand". Das würde mithilfe der beiden Befehle MERGE und D! (Delete) aber auch im laufenden Betrieb funktionieren, so wie hier beschrieben: Man löscht einen Teil des Programms und füllt dieselbe Stelle sofort wieder auf. Der Name des Programmteils, der dafür nachgeladen werden muss (hier: "level 2") wird dabei über Adresse 2 weitergegeben.



100 lv=2: poke 2,lv: call .next

5995 proc .next
5996 reset5997:fori=631to638:readx:
    pokei,x:next:poke198,8
5997 d!6000-: data 71,207,53,57,57,56,58,13
5998 reset5999:fori=631to634:readx
    pokei,x:next:poke198,4
5999 merge"level"+str$(peek(2)): data 82,213,58,13
Das Hauptprogramm muss mit folgender Zeile beginnen: 1 poke$2d,peek($ae):poke$2e,peek($af):clr

bzw. 1 d!poke$2d,d!peek($ae):clr (seit TSB v2.30803)

Der Trick ist:
Anders als beim Befehl LOAD startet ein Basic-Programm nach MERGE nicht von selbst, man muss es mit einem ausdrücklichen RUN wieder in Gang setzen. Da auch D! im Direktmodus landet, brauchen wir zweimal die Hilfe des Tastaturpuffers (ab 631, Zeilen 5996 und 5998), einmal, um nach dem Löschen der alten Code-Zeilen wieder ins Programm zurückzukehren (mit "gO5998:" DATAs in Zeile 5997) und einmal für RUN ("rU:" DATAs in Zeile 5999), um das geänderte Programm neu zu starten.

Da das nun neu zusammengesetzte Programm nicht mehr die gleiche Länge aufweist wie vorher, muss im Hauptprogramm die erste Zeile (hier: Zeile 1) dafür sorgen, dass der Interpreter über die neue Länge informiert wird. Im Anschluss müssen alle Variablen des Programms neu initialisiert werden, da sie während des Nachladens (zuerst durch RUN und dann durch CLR) verloren gegangen sind.

Achtung: Wenn man diese Zeile nicht direkt nach dem Laden des Programms ausführt (sondern zwischendurch noch andere Befehle eingibt), kann es dazu kommen, dass das Programm beschädigt wird und nicht mehr lauffähig ist, da der Interpreter die dort verwendeten Speicherstellen $ae/$af auch anderweitig verwendet.

<nach oben>


FETCH mit Vorgabestring

Eine der weniger akzeptablen Eigenschaften des FETCH-Befehls ist es, dass man einen eventuellen Vorgabestring nur anzeigen, nicht aber während des Programmlaufs editieren kann. Mit drei POKEs kann man dem abhelfen.

Der editierbare String wird hier in A$, die Maximallänge der Eingabe in FL übergeben. Die Zeile 120 begrenzt bzw. erweitert diese Maximallänge auf die Länge des Vorgabestrings.

Der FETCH-Cursor wird natürlich unmittelbar hinter den Vorgabestring positioniert (Zeile 600: AT mit addierter Stringlänge).


100 cls
105 xk=6:yk=5:a$="vorgabestring"
110 ct$=chr$(17)+chr$(29):fl=16
115 e$="Eingabe: ":a=len(a$):e=len(e$)
120 if fl<=a then fl=a+1
125 print at(xk,yk)e$a$:eingabe
130 print at(xk,lin+1)"Ausgabe: "chr$(34)a$chr$(34)
140 end

500 proc eingabe
570 for i=1 to a:poke 511+i,asc(mid$(a$,i,1)):next
590 poke $a1fa,a:poke $a1e3,$d0:poke $a1e4,$e9
600 fetch at(xk+e+a,yk)ct$,fl,a$
620 poke $a1fa,0:poke $a1e3,$a0:poke $a1e4,1
630 end proc

Der Trick ist:
Der gesamte Vorgabestring wird in den FETCH-Eingabepuffer ($0200) übertragen (Zeilen 570 und 590) und FETCH damit vorgegaukelt, es hätten bereits Tastendrücke stattgefunden.

<nach oben>


Eine TSB-Schleife vorzeitig verlassen

Manchmal ist man gezwungen, eine Schleife (eingeleitet mit LOOP oder REPEAT) vorzeitig per GOTO, CALL oder (wenn man sich in einer Prozedur befindet) mit END PROC bzw. (in Subs) mit RETURN zu verlassen. Da TSB die syntaxgerechte Beendigung dieser zwei Strukturen nicht überwacht, muss man als Programmierer in so einem Fall selbst hinter sich aufräumen, am besten über eine entsprechende von überall zugängliche Routine in Prozedurform. Räumt man nicht auf, könnte es früher oder später zu einem Stack-Overflow-Error kommen. Beispiel hier rechts mit REPEAT:


10 xa = $c617
999 : 
1000 repeat
1010 <tu was>
1020 if <irgendwas passiert> then .exit: goto 1050
1030 <tu was anderes>
1040 until <fertig>
1050 <mach weiter>
1060 ... 

2000 proc .exit
2010 ab=peek(xa): poke xa,ab+(ab>0)*2
2020 end proc

 
Die zu überwachenden Stellen (xa) lauten bei:
⇒ REPEAT: $c617
⇒ LOOP: $c641

Der Trick ist:
Der jeweilige Stackpointer wird durch einen Aufrufbefehl erhöht (REPEAT oder LOOP) und durch einen Abschlussbefehl wieder erniedrigt (UNTIL oder EXIT). Wenn der Abschlussbefehl nicht erreicht wird, nimmt der Trick das fehlende Erniedrigen "von Hand" vor.

<nach oben>


"File Exists" implementieren

Herausfinden, ob auf Disk eine Datei bereits unter dem gewünschten Namen vorhanden ist.

In TE$ ist der vollständige gesuchte Filename, AL liefert das Ergebnis, AL=0: file exists.

Wer das umgekehrt braucht: in Zeile 1970:
if al<>34 then al=0, also AL>0: file exists


10 h$=chr$(19): te$="test": file.exists

1950 proc file.exists
1960 poke 648,$c0: print h$;: dir"$:"+te$:
1965 poke 648,$04: print h$;
1970 al=peek($c02c): if al=34 then al=0
1980 end proc

Der Trick ist:
Hier wird die Ausgabe des DIR-Befehls durch POKE 648,$c0 einfach auf einen anderen "Bildschirm" (in diesem Fall dem ungenutzten Bereich bei $c000) verlegt und dort ausgelesen. So wird das Layout des aktuellen Bildschirms nicht ge- bzw. zerstört.

<nach oben>


"Blocks Free" implementieren

Mit dem gleichen Trick lässt sich auch die Blocks-Free-Angabe holen und anzeigen. Zu den Zeilen von "File Exists" kommen diese Zeilen hinzu. (Es werden die gleichen Variablen verwendet. Wegen des dortigen DIR-Befehls zuerst file.exists aufrufen.) e=3 in Zeile 2020 steht für maximal vierstellige Blocks-Free-Zahlen (wie sie bei einer 1581 oder 1571 vorkommen können).

Ergebnis wird in Variable BF zurückgeliefert.


30 h$=chr$(19): te$="": file.exists: bfree

2000 proc bfree
2010 if al then p=$c028: else p=$c050
2020 bf=0: e=3: for i=e to 0 step-1: x=peek(p+i): if x<48 or x>57 then e=e-1
2030 next: for i=0 to e
2040 x=peek(p+i)and15: x=x*10^(e-i): bf=bf+x: next
2050 end proc

Der Trick ist:
Wenn DIR nach einer Datei mit leerem Namensstring sucht, meldet es ausschließlich den Disk-Namen und die gesuchte Blocks-Free-Angabe zurück, die dann an definierter Stelle ausgelesen werden kann (in diesem Fall Zeile 1, Spalte 0: $c028, vorsichtshalber wird auch Zeile 2 bedacht: $c050, falls man bfree direkt nach einem erfolgreichen file.exists - al ist 0 - aufrufen möchte).

<nach oben>


Floppymeldung in Variablenform (DS und DS$)

Durch diesen Trick erhält man - wie in höheren Basic-Versionen - die Rückmeldungen der Diskettenlaufwerke in Variablenform.

Nach Aufruf des Programms hier rechts steht die Floppy-Antwort in der Variablen DS$ als Komplettstring zur Verfügung.

Zusätzlich steht in DS die Fehlernummer, DT$ liefert separat den Fehlertext und in TT und TS erhält man Track und Sektor des Fehlers.

Die Routine benutzt die Var-Pointer-Funktion und das Low-Hi-Splitting und wandelt Bildschirmcode nach PETSCII (Z.1135).

In Zeile 1020 wird mit colour,12 die Schreibfarbe auf die Hintergrundfarbe eingestellt, damit der Vorgang unsichtbar abläuft (bedenken, wenn andere BG-Farbe).


10 ds$="":cm$="s:test"
20 send.command:floppystatus

1000 proc send.command
1020 colour,12:poke$9d,$80:print at(0,23)"";
1030 disk cm$:poke$9d,0:colour,0
1040 end proc

 
1100 proc floppystatus
1105 ba=display+40*23
1110 sys$8b65 ds$:da=peek(780)+256*peek(782)
1120 p=0:l=32:poke da,l:
1125 d!pokeda+1,ba
1130 for i=0 to l-1:x=peek(ba+i):
1135 if x<32 then x=xor64:poke ba+i,x
1140 if x=$2c then p=p+1:if p=3 then l=i+3
1150 next:poke da,l:ds$=""+ds$
1160 ds=val(ds$):dt$=mid$(ds$,4,len(ds$)-9)
1170 tt=val(right$(ds$,5)):ts=val(right$(ds$,2))
1180 end proc


bzw. poke da+1,mod(ba,256):poke da+2,div(ba,256)
(vor TSB v2.30803)

Der Trick ist:
Da der Befehl DISK nur im Direktmodus eine Rückmeldung liefert, wird dieser Direktmodus künstlich erzeugt (über Speicherstelle $9d). Zusätzlich wird die Rückmeldung unsichtbar auf eine definierte Bildschirmposition (in Zeile 1020 und 1105) gelenkt, von wo sie dann ausgelesen und verarbeitet wird.

<nach oben>