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:
Grafikbereiche schnell highlighten
Mit den Befehlen FCHR bzw. FCOL kann man mithilfe eines einfachen POKEs bei eingeschalteter Grafik die Farben der Grafikpixel beeinflussen: POKE 648,$C0: FCHR zeile,spalte,breite,hoehe,wert: POKE 648,4. oder: POKE $A0C0,$C0: FCOL zeile,spalte,breite,hoehe,wert: POKE $A0C0,$D8. In "wert" steht die gewünschte Farbkombination: Pixelfarbe im oberen Nibble, Hintergrundfarbe im unteren (bei Hires). Die Orts- und Größenangaben beziehen sich auf einen Textbildschirm. In Multi muss man die Werte für spalte und breite verdoppeln. |
100 hires 1,0 110 poke 648,$c0 120 repeat 130 text 8,8,"test" 140 pause1 150 fchr 1,1,4,1,$50 160 pause1 170 fchr 1,1,4,1,$10 180 until peek(197)<>64 190 poke 648,4 100 hires 1,0 110 poke $a0c0,$c0 120 repeat 130 text 8,8,"test" 140 pause1 150 fcol 1,1,4,1,$50 160 pause1 170 fcol 1,1,4,1,$10 180 until peek(197)<>64 190 poke $a0c0,$d8 |
Der Trick ist: Die Ausgabe von Text in der Grafik ist generell langsamer als die Ausgabe von Text im Textmodus per PRINT. Wer dennoch den Grafikmodus für die Textausgabe verwenden möchte (z.B. für ein grafisch aufgemotztes Menü wie im Programm "Sosy"), braucht eine Möglichkeit, Menüpunkte beim Wechseln schnell zu markieren, ohne dass der Menüpunkt mit TEXT neu in anderer Farbe (highlight) geschrieben werden muss. Diese Möglichkeit ist (im Hires-Modus) die Veränderung des Video-Rams (das in Hires für die Farben der Pixel zuständig ist). In TSB liegt dieses Farb-Video-Ram an Position $C000, kann also mit dem Textmodus-Farbveränderungsbefehl FCOL nicht direkt erreicht werden (der bezieht sich auf das Color-Ram ab $D800). Will man dennoch FCOL einsetzen, verwendet man die Version mit $A0C0. |
|
|
|
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 (Token 60) und die beiden anderen sind vorbelegt mit den Schlüsselwörtern X! (61) und MAP (62), aber nicht endgültig aktiviert. |
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 (bis TSB v2.30803) |
Der Trick ist: Die drei Token waren in Simons' Basic deswegen 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. CLS ist inzwischen fest verdrahtet (seit v2.20.917), die verbliebenen zwei Positionen 61 und 62 sind trotz Vorbelegung dennoch vergebbar, denn die dort vorab eingetragenen Schlüsselwörter X! und MAP kann man natürlich auch ändern. Solange jedoch keine zugehörige Befehlsroutine implementiert wurde, kommt beim Aufruf von MAP und X! 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 $873F 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 "extinstaller.tsb" ein Installationsprogramm für solche "Extension"-Befehle. |
|
|
|
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)),u,0: data 82,213,58,13 |
Das Hauptprogramm muss mit folgender Zeile beginnen: |
1 d!poke$2d,d!peek($ae):clr bzw. 1 poke$2d,peek($ae):poke$2e,peek($af):clr (bis 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. |
|
|
|
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 nur einem POKE nach $A1FD (enthält in a die Länge des Vorgabestrings) kann man dem abhelfen (seit TSB v2.40.728). 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(yk,xk)e$a$:eingabe 130 print at(lin+1,xk)"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 $a1fd,a 600 fetch at(yk,xk+e+a)ct$,fl,a$ 620 poke $a1fd,0 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. Dieser Trick wird im Programm "ReadMe-Generator" angewendet und man kann ihn dort in Aktion erleben. Originalwert bei $A1FD ist $00. |
|
|
|
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. Das geht übrigens auch mit der DO..DONE-Verschachtelung. Will man dort vor einem DONE ausbrechen, dann stellt man den Stackpointer $c57b um einen Zähler zurück (nicht um zwei wie bei den Schleifenbefehlen): xa=$c57b:ab=peek(xa):poke xa,ab+(ab>0) |
|
|
|
Floppymeldung in Variablenform (DS und DS$ implementieren) 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(23,0)""; 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. |
|
|
|
"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. |
|
|
|
"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). |
|
|