von Harald Schönfeld Player, zum Dritten Nachdem unser kleines Progrämmlein nun bereits einen Player über den Schirm gleiten läßt und nun auch schon ein paar Hindernisse herumliegen, fehlt für ein richtiges, kleines Spielchen eigentlich nur noch ein Ziel, das über den Bildschirm huscht. Da wir ja schon wissen, wie man einen Player über den Schirm bewegt, wollen wir unser Ziel nun nicht durch einen Player darstellen lassen, sonden wir verwenden dafür Zeichensatzgrafik. Und da wir nicht im langsamen BASIC, sondern im schnellen QUICK pro- grammieren, wollen wir nicht kleckern, sondern klotzen. Und so wollen wir also einen Wurm über den Schirm bewegen, der au 10s Teilen (Zeichen) bestehen soll. Wir lassen ihn in der oberen linken Ecke starten, und wenn er den unteren Schirmrand erreicht hat, so sit das "Spiel" zu Ende (Centipede läßt grüßen). Schnelle Zeichensatzgrafik Um ein Zeichen über de Bildschirm zu bewegen, könnte man es natürlich mit Hilfe des PRINT-Befehls ausgeben, und dann mit einem weiteren PRINT(" ") wieder löschen, bevor man es an einer anderen Stelle wieder auftauchen läßt. Leider ist das nicht besonders schnell ( das Betriebssystem ist halt auch schon 13 Jährchen alt ), so daß wir es in QUICK anders machen werden. Wir schreiben unser Zeichen lieber direkt in den Bildschirmspeicher.Als Basis für dieses Unterfangen dient uns die OS-Variable SAVMSC (88/89), in der die BS-Anfangsadresse zu finden ist. Schreiben (POKEn) wir einen Wert in diese Adresse, so wird unser Zeichen in der linken oberen Ecke des Schrims dargestellt. Wollen wir das Zeichen dann bewegen, so müssen wir die Adresse,an die wir POKEn wollen, verändern. Und zwar nach folgendem Schema: - 1 addieren, um eine Position weiter nach rechts zu gelangen - 1 subtrahieren, um eine Position nach links zu kommen - 40 addieren, um eine Zeile nach unten zu gelangen. Der Wert 40 kommt daher, daß wir in Grafik 12 ja 40 Zeichen (40 Bytes) pro Zeile darstellen (verbrauchen). Ein Wurm - 10 Zeichen Ein Zeichen macht noch keinen Wurm (wer hätte das gedacht!). Deshalb verwenden wir 10 Zeichen, um den Wurm auf den Bildschirm zu setzen. Um den Wurm zu "verwalten" legen wir uns ein Feld an, in dem die Adressen, an die die Zeichen auf den Schirm gepoked werden sollen, gespeichert werden. Da diese Adressen WORDs sein müssen, muß das Feld 10*2 Bytes lang sein: (Runde Klammern mit einem Pluszeichen stehen für eckige Klammern) ARRAY (+ PARTS(20) )+ Zu Anfang soll der Wurm in der linken oberen Ecke sitzen. Also müssen wir die Werte +1...10, in das Feld schreiben. Da wir in QUICK ein Feld aber nur mit festen Werten initialisieren können, schreiben wir erst 10 bis 1 hinein... * Wurmteiladressen (relativ zum * BS Anfang) DATA(PARTS) (+ 10,0,9,0,8,0,7,0,6,0,5,0,4,0 3,0,2,0,1,0 )+ ... und dann zählen wir in einer Schleife zu allen 10 WORDs den Inhalt von SAVMSC dazu: I=0 REPEAT W=PARTS(I) ADD(W,SAVMSC,W) POKE(W,84) PARTS(I)=W I+ I+ UNTIL I=20 I muß immer um 2 erhöht werden, weil wir ein WORD-Feld verwalten müssen (W ist auch ein WORD!). Der POKE-Befehl schreibt dann unsere 10 Wurm-Zeichen (Interer Zeichencode 84) in den BS-Speicher. Die eigentliche Bewegung des Wurms erfolgt - wie könnte es anders sein - ruck- und flackerfrei im schon vorhandenen VBI: INTER MOVE_SHOOT_WORM BEGIN ZPUSH .STICK .PLAYER .SHOOT .WORM ZPULL ENDVBI WORM bewegt den Wurm. Auffallend sind die beiden neuen Befehle ZPUSH und ZPULL. Was sie tun (sollten!!! Sorry), wird am Ende erklärt. Die eigentliche Bewegung ist nun eigentlich recht einfach: Die OS-Variable RANDOM liefert uns eine Zufallszahl von 0 bis 255. Mit AND 31 maskieren wir die oberen 3 Bits aus, so daß eine Zahl RND im Bereich von 0 bis 31 übrigbleibt. Da sich der Wurm meist nach links oder rechts und nur selten nach unten bewegen soll, wählen wir uns als neue Wurmkopfadresse je nach RND ADR-1, ADR+1 oder ADR+40, wobei ADR+40 nur in einem von 32 Fällen auftritt. Da unser Wurm die Hindernisse nicht überrennen darf, sehen wir nach Berechung der neuen Adresse nach, ob diese auch wirklich frei ist (PEEK). Ist das nicht der Fall bemühen wir den Zufall nochmal. Ansonsten müssen wir natürlich prüfen, ob unser Wurm nicht schon den BS-Speicher verlassen würde. In diesem Fall wäre das Spiel zu Ende. Schließlich können wir jedoch zunächst das Schwanzteil vom Schrim löschen. Die Mittelteile bleiben unverändert, und nur das Kopfteil müssen wir an einer neuen Stelle auf den Schirm poken. Allerdings müssen wir die Adressen der Mittelteile um 1 nach hiten schieben, denn das ehemals vorletzte Teil ist ja nun das letzte,..., das erste das zweite, und das neue erste bekommt die neu berechnete Adresse. Alles klar? Na der folgende Programmteil zeigt's in wenigen Worten: WNAZ gibt dabei die Nummer des letzten Teil an. PROC WORM LOCAL BYTE (+ I,J,RND,OK )+ WORD (+ ADR,NEWADR,FADR )+ BEGIN * erstes Teil noch nicht am BS-Ende? FADR=PARTS(0) IF FADR16 NEWADR=-1 ELSE NEWADR=40 ENDIF ENDIF ADD(FADR,NEWADR,NEWADR) * neue Pos. frei? PEEK(NEWADR,OK) UNTIL OK=0 * BS-Ende erreicht? IF NEWADR>ENDBS NEWADR=ENDBS * Also ENDE erreicht ENDE=1 * nichts mehr tun, PROC verlassen JUMP(1) ENDIF ENDIF * letztes Teil vom BS loeschen ADR=PARTS(WANZ) POKE(ADR,0) * Adressen um 1 Teil verschieben J=WANZ SUB(J,2,I) REPEAT ADR=PARTS(I) PARTS(J)=ADR I- I- J- J- UNTIL J=0 * neue Adresse des 1. Teils setzen PARTS(0)=NEWADR POKE(NEWADR,84) -1 ENDPROC Wenn wir aus dem Ganzen ein Spiel wie Centipede machen wollen, so müssen wir natürlich den Wurm vertilgen können. Nun, einen Schuß können wir ja schon abfeuern. Wir müssen also nur noch prüfen, ob dieser Schuß ein Wurmteil getroffen hat. Wie im letzten Teil erklärt, verwendet man dazu einfach die Player-Playfield Kollisionregister. Im Gegensatz zu vorher müssen wir allerdings 2 Dinge beachten: - Ein Treffer auf den Wurm oder ein Hindernis stoppt den Schuß. Das ist der Fall, wenn irgendwas <>0 im Register steht. - Ein Treffer auf den Wurm, kostet den Wurm sein jeweiliges Schwanzstück. Dieser Treffer ist eingetreten, wenn im Register das Bit 0,1 oder 2 gesetzt ist. Um an diese Bits heranzukommen verwenden wir den AND 7 Befehl, der alles außer diesen 3 Bits auf 0 setzt. Näheres siehe Erklärung von AND, OR, EOR. Haben wir den Wurm getroffe, so löschen wir das letzte Teil vom BS, erniedrigen die Anzahl der noch verbliebenen Teile WANZ um 1 (= 2 in Bytesrechnung!) und lassen den Bildschirm kurz aufflackern: * Kollision Schuss mit Objekt? AND(P1PF,15,HITTEST) IF HITTEST<>0 * Schuss loeschen PLAYER($75,SY,5,NOSHOTADR) SY=34 * Schuss auf Endposition ENDIF * Treffer Schuss mit Wurm? AND(P1PF,7,HITTEST) IF HITTEST<>0 * Adresse des letzten Teil holen CADR=PARTS(WANZ) * leztes Teil vom BS loeschen POKE(CADR,0) * Teilanzahl um 1 (2*1) erniedrigen WANZ- WANZ- * Wenn Teilezahl<0 (0 minus 2 = 265-2) * dann ENDE erreicht * Trefferblitz SETCOL(4,15,15) IF WANZ=254 ENDE=1 ENDIF ELSE SETCOL(4,0,0) ENDIF HITCLR=0 * Kollision loeschen Um aus diesem Grundgerüst ein richtiges Spiel zu machen, fehlen noch ein paar Dinge. Z.B.: - Aufteilen des Wurms bei Treffern - Kollision Wurm - Player - Etwas mehr Sounds - Ein weiterer Gegner So, da die Wurmbewegung im VBI läuft haben wir nun 2 Probleme: - Der Wurm ist viel zu schnell Naja, das ist nicht schlimm. Wir verwenden eine Variable, die wir pro VBI um 1 erhöhen. Hat sie einen bestimmten Wert (z.B. 5) erreicht, so setzen wir sie wieder auf Null und bewegen erst in diesem Fall den Wurm (also jedes 5te mal). Das ist im kompletten QUICK-Listing dann auch verwirklicht. WORMWAIT gibt dort an, wieviel VBI's stattfinden müssen, bis der Wurm bewegt wird. - Da wir im VBI jetzt einen im Handbuch mit "*" gekennzeichneten Befehl benutzen, müssen wir vor jeder Ausführung des VBI interne QUICK-Variablen retten (ZPUSH) und am Ende des VBI wieder restaurieren (ZPULL). Man muß sich eben vorstellen, daß die QUICK-internen Routinen vom VBI unterbrochen werden, dort dann aber selbst QUICK-interne Routinen verwendet werden, die dann natürlich alle möglichen internen Variablen der RUNTIME.OBJ verändern. Dieser mit "*" gekennzeichnete Befehl ist POKE und PEEK. Einfache, ungekennzeichnete Befehle wie SUB, ADD, SETCOLOR,... benötigen alleine kein ZPUSH und ZPULL. Achtung: QUICK-BUG!!! Eigentlich wäre nun alles klar, aber wenn jetzt jemand versucht, parallel zum VBI wirklich noch eine Hauptschleife laufen zu lassen (die also vom VBI in 1/50 Sekunden Abständen kurz unterbrochen wird), so wird er feststellen, daß der XL öfters jämmerlich abstürzt. Woran liegt's? ZPUSH und ZPULL funktionieren mit PEEK und POKE nicht richtig (mit anderen "*" Befehlen schon, aber PEEK und POKE sind eben "igitt"). Wie kann man sich helfen? Man muß zusätzlich zu ZPUSH und ZPULL einen Kopierbefehl verwenden, der die obere Hälfte der Zeropage (ca. Adresse 100 bis 255) in einem Buffer z.B. Page 6 (1536 bis 1791) sichert und am Ende des VBI wieder restauriert. Dazu verwendet man z.B. den Playerbefehl, der ja zum kopieren von 0 bis 255 Bytes gut geeignet ist. Mit diesem Trick kann man diesen Fehler in QUICK wirklich umgehen . Rubberball ist der Beweis dafür.