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:
Joystick und Tastatur gleichzeitig abfragen Vor allem in Spielen möchte man Objekte gern sowohl mit der Tastatur als auch mit einem Joystick steuern können. TSB liefert mit der JOY-Funktion und dem GET-Kommando die entsprechenden Werkzeuge. Entscheidend ist dann aber die PLACE-Funktion. | 100 w$="{crsr up} {crsr rgt} {crsr dwn} {crsr lft} " 110 steuerung 2000 proc steuerung 2010 repeat: get x$: x=joy(2): x=-x*((x and 1)>0) 2020 if x=0 then x=place(x$,w$) 2030 until x 2040 if x=1 then ... 2050 if x=3 then ... 2060 if x=5 then ... 2070 if x=7 then ... ... |
|
Der Trick ist: Sowohl die Tastatur- als auch die Joystick-Eingaben werden in Zahlenwerte umgewandelt. Wichtig sind dabei die vier Richtungen nach oben (1), nach rechts (3), nach unten (5) und nach links (7). Die PETSCII-Zahlenwerte der Cursortasten (145, 29, 17 und 157) bieten von sich aus leider keine wirklich praktikable algorithmisierbare Möglichkeit der Zuordnung zu diesen Richtungszahlenwerten. Erst ein Referenz-String schafft da Abhilfe, hier im Beispiel deklariert als W$. Er enthält die PETSCII-Steuerzeichen der vier Cursortasten, und zwar genau an den Positionen 1, 3, 5, und 7 im String, das sind die Werte, die auch von JOY geliefert werden. Enthält X$ die eingegebene Cursortaste, dann ist x=PLACE(x$,w$) der zugehörige Richtungszahlenwert. Et voilà! (Die Feuertaste wird hier nicht berücksichtigt, eine diagonale Richtung ebenfalls nicht. Feuertaste mit f=x and 128 gleich nach der JOY-Abfrage in Z. 2010 und dann in Z. 2030 until x or f.) |
||
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. Er verwendet zur Ausführung den Inhalt der Zeile 23 des Bildschirms. send.command ist eine eigenständige Routine, die auch für andere Zwecke verwendet werden kann. Sie verändert den Inhalt der Bildschirmzeile 23. Nach Aufruf des Programms hier rechts steht die Floppy-Antwort in der Variablen DS$ als Komplettstring zur Verfügung. Zusätzlich findet sich in DS die Fehlernummer, DT$ liefert separat den Fehlertext und in TT und SS erhält man Track und Sektor des Fehlers. Die Routine benutzt die Var-Pointer-Funktion und (ursprünglich einmal) das Low-Hi-Splitting und wandelt rudimentär Bildschirmcode nach PETSCII (Z.1135). |
10 cm$="s:test" 20 send.command:color,c1:floppystatus:color,c2 30 print ds$ [...] 1000 proc send.command 1010 c1=peek($d021) and 15:c2=peek(646) and 15 1020 color,c1:poke$9d,$80:print at(23,0)""; 1030 disk cm$:poke$9d,0:color,c2 1040 end proc 1100 proc floppystatus 1105 ba=display+40*23: ds$="" 1110 da=0*len(ds$)+d!peek(71) 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$,5,len(ds$)-10) 1170 tt=val(right$(ds$,5)):ss=val(right$(ds$,2)) 1180 end proc bzw. 1125 poke da+1,mod(ba,256):poke da+2,div(ba,256) (vor TSB v2.30803, Hi-Lo-Splitting) |
|
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. In Zeile 1020 wird mit color,c1 die Schreibfarbe auf die Hintergrundfarbe eingestellt, damit der Vorgang unsichtbar abläuft (hierfür dient die Zeile 1010, die AND-Verknüpfung dort ist erforderlich, siehe COLOR). |
||
|
||
"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). |
||
|