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:


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. 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

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 $b061,a:poke $b04a,$d0:poke $b04b,$e9
600 fetch at(xk+e+a,yk)ct$,fl,a$
620 poke $b061,0:poke $b04a,$a0:poke $b04b,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 Struktur vorzeitig verlassen

Manchmal ist man gezwungen, eine Struktur (eingeleitet mit LOOP, REPEAT oder DO) 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 drei 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:


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

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

 
Die zu überwachenden Stellen (xa) lauten bei:



Bei DO muss die Zeile 2010 anders lauten:
⇒ REPEAT: $c617
⇒ LOOP: $c641
⇒ DO: $cbe4

2010 ab=peek(xa): poke xa,ab+(ab>0)

Der Trick ist:
Der jeweilige Stackpointer wird durch einen Aufrufbefehl erhöht und durch einen Abschlussbefehl wieder erniedrigt (z.B. wie hier: REPEAT und UNTIL). 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/i=3 stehen für maximal dreistellige Blocks-Free-Zahlen (beachten bei 1581 oder 1571: dort beide Werte auf 4 oder gleich von vornherein auf 4).

Ergebnis in BF


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=3 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$80bd ds$:da=peek(780)+256*peek(782)
1120 p=0:l=32:poke da,l:
1125 poke da+1,mod(ba,256):poke da+2,div(ba,256)
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

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>