Den Hintergrund bewegen
Eben haben wir unser Puzzle so geändert, dass wir die Sprites über Tabellen positionieren, aber der Hintergrund des jeweiligen Puzzleteils bliebt unverändert. Das sollten wir jetzt schleunigst ändern.
Vorbereitung
Wir wollen einfach die Quarate für den Spritehintergrund abhängig von unserer Tabelle tileOrder, in der ja die Puzzleteile für das jeweilige Feld zufinden sind, zeichnen. Um jetzt die Teile in das jeweilige Feld zu zeichnen, benötigen wir natürlich deren Position. Werfen wir dazu noch mal einen Blick auf unser Spielfeld:
Der Grafik könnt ihr nun entnehmen, wo (Spalte X, Zeile Y) jeweils die obere/linke Ecke eines Feldes liegt. Dass hier alles Symetrisch ist, ist übrigens reiner Zufall bzw. meinem Symetriewahn geschuldet 🙂 . Wir werden nun wieder die Tabelle tileOrder von hinten nach vorne abklappern. Dadurch beginnen wir im 9. Feld, setzen dann die 5*5 Zeichen und kommen dann zu Feld 8, 7, usw. Ich habe bewußt auf jegliche Optimierung verzichtet und hoffe, dass ich euch nun im Source das genaue Vorgehen erklären kann. Die obige Grafik werde ich für meine Ausführungen gleich noch häufiger bemühen.
Eine weitere Funktion hinzufügen
Fügt die Routine am Besten hinter dem rts von spriteSetPositions ein.
;******************************************************************************* ;*** Hintergründe der einzelnen Puzzleteile zeichnen ;*** gerade Sprite-Nr. = heller Hintergrund ;*** ungerade Sprite-Nr. = dunkler Hintergrund ;*** $ff = freies Feld ;******************************************************************************* ;*** Übergabe: Abhängig von tileorder: ;******************************************************************************* ;*** Rückgabe: - ;******************************************************************************* ;*** ändert : A, X, Y, SR ;******************************************************************************* !zone drawTiles drawTiles lda #<(SCREENRAMADR+(15*40+15)) ;ZP-Adresse für den BS-Speicher sta ZP_SCREENRAMADR ;auf die linke obere Ecke lda #>(SCREENRAMADR+(15*40+15)) ;des 9. Feldes setzen sta ZP_SCREENRAMADR+1 lda #<(COLORRAMADR+(15*40+15)) ;ZP-Adresse für das Color-RAM sta ZP_COLORRAMADR ;auf die linke obere Ecke lda #>(COLORRAMADR+(15*40+15)) ;des 9. Feldes setzen sta ZP_COLORRAMADR+1
Hinter drawTiles beginnen wir zunächst damit, die Adresse der linken oberen Ecke des neunten Feldes zu ermitteln und auf der Zero-Page zu speichern. Die Variablen sollten euch aus der Funktion drawScreen bekannt vorkommen. Um die Adresse zu berechnen, werft noch mal einen Blick auf die Grafik. Wie ihr seht, beginnt das 9. Feld in Zeile 15 (denkt daran, dass auch hier die Zählung bei 0 beginnt)! Wir können also einfach 15 * 40 Zeichen überspringen. Danach stehen wir auf dem ersten Zeichen, von Zeile 15. Da das Feld in Spalte 15 (auch hier startet die Zählung bei 0) beginnt, müssen wir noch 15 weitere Zeichen überspringen (denkt dran, wir standen bereits auf dem 1. Feld), somit landen wir also bei einem Offset von 600 + 15 = 615 oder $267. Diese Werte könntet ihr auch direkt einsetzen, ich finde es aber schöner, den Assembler die Berechnung ausführen zu lassen. Denkt daran, dass solche Berechnungen keinen Einfluss auf die Ausführungsgeschwindigkeit des fertigen Programms haben. Der Assembler übernimmt bei der Programmerstellung einfach nur das Ergebnis der Formel. Da wir auch die Farbe setzten müssen, addieren wir diesen Offset nicht nur zur Startadresse des Bildschimspeichers, sondern auch zu der des COLOR-RAM.
Zunächst benötigen wir eine neue Konstante ZP_LOOPHELPER, die wir für alle möglichen Schleifen-Operationen verwenden können. Für eine etwas bessere Performance verwenden wir die Zero-Page, wir reservieren uns mal die nächsten 8-Bytes ab der Adresse, auf die die Konstante verweist, sprich die letzten acht Bytes der Zero-Page.
Also einfach die Konstante oben hinzufügen…
ZP_LOOPHELPER = $f8 ;Die letzten 8 BYTES für Schleifen reservieren
…und dann geht es weiter, mit unserer Routine.
ldx #$08 ;max. 9 Felder .loop txa ;X-Register auf dem pha ;Stack merken ldy #COLOR_GREY ;Erstmal grau ins Y-Register lda tileOrder,X ;was steht im Feld -> Akku bmi .skip ;wenn freies Feld -> .skip and #%00000001 ;gerades oder ungerades Sprite? bne .skip1 ;wenn ungerade, Farbe behalten -> .skip1 ldy #COLOR_LIGHTGREY ;Hintergrund für gerades Sprite .skip1 lda #160 ;invertiertes Leerzeichen in den Akku jmp .skip2 .skip ;freies Feld lda #102 ;Karriertes Zeichen in den Akku ldy #COLOR_DARKGREY .skip2 sta ZP_LOOPHELPER ;Zeichen auf die Zero-Page sty ZP_LOOPHELPER+1 ;Farbe auf die Zero-Page
Als nächstes laden wir unsere Schleifenvariable für die Felder ins X-Register. Da wir das X-Register aber gleich für etwas Anderes benötigen, sichern wir es hinter .loop erstmal auf dem Stack. Im Y-Register wollen wir uns die Farbe für das jeweilige Puzzleteil merken, wir gehen zu Beginn von grau für die ungeraden Puzzleteile aus. Dann holen wir uns das Teil, dass auf dem letzten Feld liegt (wir schauen uns ja gerade den ersten Durchlauf der Schleife an und beginnen beim letzten Feld) in den Akku. Ist der Wert negativ, handelt es sich um das leere Feld und wir springen zu .skip. Dort laden wir das Zeichen für das karrierte leere Feld in den Akku und die Farbe dunkelgrau ins Y-Register. Ist der Wert nicht negativ, dann prüfen wir, ob es sich um ein gerades oder ungerades Puzzleteil handelt. Ist es ungerade, springen wir direkt zu .skip1; ist es gerade, ändern wir die Farbe im Y-Register auf hellgrau und landen dann auch bei .skip1. Hier wird nun das invertierte Leerzeichen in den Akku geladen und wir springen zu .skip2. Dort haben wir im Akku also unser Zeichen und im Y-Register die Farbe für das Puzzleteil. Da wir die beiden Register brauchen, speichern wir deren Werte auf der Zero-Page und beginnen mit dem Zeichnen.
ldx #$04 ;Zeile ins X-Register .loop1 ldy #$04 ;Spalte in Y-Register .loop2 lda ZP_LOOPHELPER ;Zeichen holen sta (ZP_SCREENRAMADR),Y ;ab in den BS-Speicher lda ZP_LOOPHELPER+1 ;Farbe holen sta (ZP_COLORRAMADR),Y ;und ins Farb-RAM dey ;Spalte verringern bpl .loop2 ;solange positiv nächstes Zeichen ausgeben
Da wir unser X-Register für die Hauptschleife auf dem Stack gesichert haben, können wir es jetzt für eine weitere Schleife benutzten. Wir legen in X die Anzahl der Zeilen und im Y-Register die Anzahl der Zeichen je Zeile ab. Da unsere Puzzleteile 5*5 Zeichen groß sind, schreiben wir also jeweils eine 4 (wir prüfen mit bpl) ins Register. Dann holen wir das Zeichen in den Akku und schreiben es per Y-nach-indizierter Adressierung in den Bildschirmspeicher, das Gleiche machen wir danach mit der Farbe und dem COLOR-RAM. Dann verringern wir das Y-Register und vervollständigen die Zeile.
Sobald die Y-Schleife abgearbeitet wurde, haben wir die erste Zeile von rechts nach links gezeichnet. Jetzt müssen in der darunterliegenden Zeile weitermachen. Dazu ist es wichtig, dass ihr euch erinnert, dass wir bei ZP_SCREENRAMADR die Adresse der linken oberen Ecke des 9. Feldes gespeichert haben ($0400 + $267).
clc lda ZP_SCREENRAMADR ;LSB der BS-Speicherpostion holen adc #$28 ;eine Zeile (40 BYTES) addieren sta ZP_SCREENRAMADR ;und wieder speichern bcc .skip3 ;wenn kein Carry weiter springen inc ZP_SCREENRAMADR+1 ;sonst MSB erhöhen
Wir addieren jetzt auf das LSB unserer Adresse in ZP_SCREENRAMADR die 40 Zeichen, für eine ganze Zeile. Wenn das Carry-Flag gelöscht ist, springen wir weiter, sonst erhöhen wir das MSB um eins.
.skip3 ;das Gleiche wie eben, nur fürs Farb-RAM clc lda ZP_COLORRAMADR adc #$28 sta ZP_COLORRAMADR bcc .skip4 inc ZP_COLORRAMADR+1 .skip4 dex ;Zeilenzähler verringern bpl .loop1 ;solange positiv, nächste Zeile
Auch beim Farb-RAM addieren wir 40. Zum Schluß verringern wir das X-Register, in dem wir hier ja unsere Zeilen zählen und zeichnen noch die nächsten vier Zeilen.
Jetzt ist ein komplettes Feld fertig, wir müssen also zum Beginn des nächsten wechseln. Wenn wir z. B. gerade das 9. Feld gezeichnet haben, dann befinden wir uns nun in der Zeile 20 (nach jeder Zeile haben wir 40 addiert) und in Spalte 15 (die Zeichen für eine Zeile werden absteigend von rechts nach links ausgegeben). Wir müssen nun also fünf Zeilen nach oben und dann noch 5 Zeichen nach links (also 5 * 40 + 5 = 205 Zeichen abziehen)…
sec lda ZP_SCREENRAMADR ;nun die Startposition vom Feld sbc #$CD ;links berechnen, dazu 205 abziehen sta ZP_SCREENRAMADR ;da wir eben bereits 40 addiert haben bcs .skip5 dec ZP_SCREENRAMADR+1 .skip5 ;auch fürs Farb-RAM sec lda ZP_COLORRAMADR sbc #$CD sta ZP_COLORRAMADR bcs .skip6 dec ZP_COLORRAMADR+1
…und genau das hat dieser Abschnitt für den Bildschirmspeicher und das Farb-RAM gemacht.
Kommen wir endlich zum Schluss von drawTiles…
.skip6 pla ;Stack wieder zurück tax ;ins X-Register cmp #$06 ;untere 3 Felder fertig? beq .rowUp ;falls ja, einen Block nach oben cmp #$03 ;mittlere drei Felder fertig? bne .skip7 ;falls nein, nächstes Feld jmp .rowUp ;sonst einen Block nach oben .skip7 dex ;X (jetzt wieder Feld-Nr.) verringern bpl .loop ;schon alle 9 Felder verarbeitet? rts ;zurück
Jetzt holen wir unseren Zähler für die Felder wieder vom Stack (s. oben) und kontrollieren dann, ob wir alle drei Felder in der unteren oder mittleren Reihe gezeichnet haben. Sollte das der Fall sein, dann müssen wir zum Beginn des 6. oder 3. Feldes (s. Grafik ganz oben) springen. Zum Schluß zählen wir die Feld-Nr. im X-Register herunter und beginnen wieder weiter oben mit dem nächsten Feld oder sind fertig und verlassen die Routine.
Solltet ihr euch jetzt fragen, warum .rowUp hinter dem rts folgt, dann könnt ihr den Block ja gerne mal vors .skip7 verschieben und euch ggf. nochmal die Branch-Befehle anschauen.
.rowUp sec lda ZP_SCREENRAMADR ;immer wenn drei Felder gezeichnet wurden, sbc #$b9 ;müssen wir einen Block nach oben, dazu sta ZP_SCREENRAMADR ;185 abziehen bcs .skip8 ;Achtung oben wurden bereits 205 subtrahiert! dec ZP_SCREENRAMADR+1 .skip8 ;auch hier wieder das gleiche fürs Farb-RAM sec lda ZP_COLORRAMADR sbc #$b9 sta ZP_COLORRAMADR bcs .skip7 dec ZP_COLORRAMADR+1 jmp .skip7 ;weiter mit dem nächsten Feld
Haben wir z. B. die untere Reihe (Feld 9, 8 und 7) vollständig gezeichnet, dann befinden wir uns in Zeile 15 in der ersten Spalte. Wie ihr euch erinnert, haben wir nach jedem Feld den Beginn des links davon liegenden berechnet, das wurde auch nach Feld 7 gemacht und wir sind dann im Niemandsland gelandet. Um jetzt zur linken oberen Ecke von Feld 6 zu gelangen müssen wir vier ganze Zeilen und 25 Zeichen, also (4 * 40 + 25 = 185 Zeichen) abziehen. Das machen wir natürlich, wie jedes Mal, für den Bildschirmspeicher und das Farb-RAM. Von hier müssen wir fürs nächste Feld per jmp .skip7 zurück zur Hauptroutine springen.
Damit wir die Tiles auch sehen, fehlt natürlich noch der Aufruf von drawTiles. Fügt in die Funktion loadSprites, direkt hinter jsr spriteSetPositions die folgende Zeile ein:
jsr drawTiles ;Hintergründe zeichnen
Damit wäre dies auch erledigt. Wer nun fürchtet, dass das alles zu langsam ist, es sieht ja schließlich sehr aufwendig aus, den kann ich beruhigen: Es mag nicht sonderlich schnell sein, für diesen Zweck ist es aber mehr als ausreichend. Wer sich das Projekt herunterlädt, der findet im Source sogar eine Funktion slowDown, mit der ihr die Ausgabe verlangsamen könnt, damit ihr nochmal genau seht, wie die Felder gezeichnet werden. Ihr müsst nur oben im Source, vor der Konstanten SLOWDOWN, das Semikolon löschen oder vom Diskettenimage „LOVE SLOW“ laden.
Da wir die Felder jetzt selbst zeichnen, habe ich sie aus unserem Bildschirm „Background.charscreen“ im Char Screen Editor wieder gelöscht.
Wenn ihr das Programm jetzt mit dem Ende von Puzzle 004 vergleicht, dann seht ihr unseren Fortschritt.
Da sind wir auch schon wieder am Ende angelangt, als nächstes sollten wir das Puzzle endlich mal vom Programm automatisch durchwürfeln lassen.
Unsere Routine ist jetzt so groß geworden, dass ein bedingter Sprung an den Anfang zu .loop über 128 Bytes entfernt wäre, wenn wir .rowUp auch noch in die Hauptschleife aufnehmen. Aber das Ende der Funktion ist nicht so weit entfernt und wir können daher unsere benötigten Zeilen dahin auslagern.
Hier wieder der Download:
Schritt 5 - Puzzle 005
Der Build in dieser Phase geht schon wieder nicht mehr im CBM Prg Studio 3.0. Sachen wie “lda #<SCREENRAMADR+$267" werden als invalid operand angezeigt, und die cheap labels funktionieren hier auch nicht mehr.
Ich dachte ursprünglich es läge an meinem Code, jedoch bekomme ich mit deiner Datei genau die selben fehler wenn ich einen Build machen will.
Es macht die Sache zwar nicht besser, aber es ist kein Problem von 3.0.0.
Die beschriebenen Fehler gehen auf Umstellungen zurück, die ab Version 2.8.0 Einzug ins CBM prg Studio hielten.
Das meiste habe ich damals überarbeitet, aber anscheinend nicht alles. Sorry!
Damit du schnell weiter kommst…
Um das ‘
lda #>SCREENRAMADR+$267
‘-Problem zu beheben füge einfach die Zeile ‘Operator Calc
‘ am Programmbeginn ein.Die Sichtbarkeit der Cheap-Label wurde ab 2.8.0 geändert. Daher führt
@loop:
...
beq neuesLabel:
...
neuesLabel:
...
bne @loop:
zu einem Fehler. Cheap-Label sind nur bis zum nächsten ‘normalen’ Label sichtbar. Du kannst also einfach aus
neuesLabel:
ein CheapLabel@neuesLabel:
machen.Auch hier ist es wieder interessant, wie viele die Seite in der Zwischenzeit gelesen und das ZIP heruntergeladen haben, ohne etwas zu sagen.
Daher vielen Dank für die Rückmeldung! Ich schaue es mir demnächst nochmal an und korrigiere die entsprechenden Passagen und Downloads.
So, ich habe die Erklärung der Cheap-Label in Puzzle 004 überarbeitet und die Sourcen (inkl. Download) für Puzzel 005 korrigiert. Der Rest folgt in den nächsten Tagen.
Vielen Dank Jörn für deine Hilfe und die ganzen Erklärungen. Jetzt kann ich weitermachen. Super. 🙂
Gern geschehen, die Beispiele sollen ja funktionieren, sonst macht das alles keinen Sinn.
Ich habe jetzt auch den Rest des Puzzles überarbeitet (es betraf nur noch die Downloads).