Erstellt: 5. Mai 2013 (zuletzt geändert: 1. November 2021)

Puzzle 004

Lauter Tabellen

C64 Studio

Es wird Zeit, das Bewegen der Puzzleteile in Angriff zu nehmen. Wie wollen wir das jetzt umsetzt? Ich schlage vor, wir nummerieren die Felder zunächst von 1 bis 9 durch:

Love_1_4_1Dann merken wir uns in einer Tabelle tileOrder, welches Puzzleteil sich auf welchem Feld befindet.
Tabellen werden von euch sehr häufig benötigt, z. B. für Color-Cycling-Effekte oder wenn ihr etwas Sinusförmig bewegen möchtet. Daher verwenden wir zur Übung gleich mal drei davon, auch wenn wir einige Werte durchaus zur Laufzeit berechnen könnten.
Da wir den Ansatz Puzzleteil = Sprite-Nr. verfolgen, wissen wir so auch gleich welches Sprite auf welchem Feld zufinden ist. Wir speichern einfach die jeweilige Sprite-Nr. in der Tabelle, allerdings müssen wir noch das leere Feld (oben die Nr. 9) beachten. Um dies zu erkennen speichern wir dafür $ff in unserer Tabelle.

Das Programm anpassen

Fügt das Label tileOrder und die !byte-Anweisungen am Besten hinter dem Hilfsregister calc16Bit ein.

;*** Was befindet sich auf dem jeweiligen Feld?
;*** 0-7: Sprite-Nr. = Puzzleteil
;*** $ff: leeres Feld
tileOrder
 ;Feld   1    2    3  -------
 !byte $00, $01, $02 ;|1|2|3|
 ;Feld   4    5    6  -------
 !byte $03, $04, $05 ;|4|5|6|
 ;Feld   7    8    9  -------
 !byte $06, $07, $ff ;|7|8|9|
                     ;-------

Die obige Tabelle enthält wieder die Positionen, so wie sie bei der Lösung des Puzzles vorliegen müssen. Wie ihr seht, können wir jetzt auch sehr einfach erkennen, ob das Puzzle gelöst wurde. Im letzten Feld muss das leere stehen und davor müssen die Nr. aufsteigend sortiert sein. Dass ich die Tabelle als 3*3 Bytes angelegt habe, ist nur ein Zugeständnis an unser Vorstellungsvermögen. So fällt es uns leichter, die jeweilige Position zu erkennen. Wir hätten die Werte auch alle direkt hintereinander in eine !byte-Anweisung schreiben können, so wie sie später auch im Speicher zu finden sind. Die „optische“ Aufbereitung im Source hat also keinen Einfluß darauf, wie die Bytes später im Speicher abgelegt sind. Wer möchte kann auch alle neun Bytes einzeln untereinander schreiben, das macht keinen Unterschied.

Um die Sprites zu positionieren, speichern wir uns als nächstes einfach die möglichen Startkoordinaten für jedes Feld in einer kleinen Tabelle. Da wir darauf geachtet haben, dass kein Sprite an einer X Position über 255 gezeichnet werden muss, reichen uns zwei Bytes, je eins für die X- und Y-Position.

Fügt das Label spritePos und die !byte-Anweisungen direkt hinter der Tabelle von eben ein.

spritePos
 ;*** Spriteposition für jedes Feld (s. o.)
 !byte $40, $5a                     ;Feld 1  (X, Y)
 !byte $68, $5a                     ;Feld 2  (X, Y)
 !byte $90, $5a
 !byte $40, $82
 !byte $68, $82                     ;...
 !byte $90, $82
 !byte $40, $aa
 !byte $68, $aa                     ;Feld 8  (X, Y)
 !byte $90, $aa                     ;Feld 9  (X, Y)

Hier findet ihr nun die Werte, wie wir sie im vorherigen Beitrag direkt beim Laden der Sprites verwendet haben. Einzige Ausnahme bilden die Koordinaten fürs 9. Feld. Da wir die Puzzleteile verschieben müssen, landen natürlich auch Sprites unten rechts. Wie ihr evtl. in der Tabelle besser seht, ließen sich die X und Y Position auch einfach berechnen.

Die letzte Tabelle könnten wir uns wirklich sparen, aber im Sinne der Übung, legen wir noch eine an. Möchten wir jetzt z. B. für das 3. Feld die Sprite-Position haben, dann müssen wir ja den Anfang ermitteln. Das kann man natürlich ganz einfach mit (Feld-1)*2 berechnen, aber wir benutzen, wie angedroht, noch eine Tabelle.
Fügt die Tabelle vor dem Label spritepos ein.

spritePosHelper
 !for I = 0 TO 8
    !byte I*2
 !end

Hier seht ihr, wie man eine Tabelle auch automatisch vom Assember erstellen lassen kann. Was !for macht, solltet ihr bereits aus dem BASIC-Teil wissen. Die Syntax für den Assembler ist zwar geringfügig anders und statt NEXT gibt es ein !end, die Funktionsweise ist aber identisch.
Wir erzeugen hier automatisch neun !byte-Anweisungen und lassen den jeweiligen Wert mit I*2 vom Assembler berechnen. Ihr könntet alternativ auch !byte $00, $02, $04, $06, $08, $0a, $0c, $0e, $10 schreiben. Hier ist dieses Vorgehen evtl. etwas überzogen, geht es aber um hunderte von Werten, spart ihr euch viel Tipparbeit.

Routine zum Positionieren der Sprites

OK, wir haben jetzt lauter Tabellen, nun wollen wir die auch mal verwenden. Wir schreiben eine Routine, die alle Sprite an Hand der Tabellen setzt.

Fügt die Funktion einfach hinter dem rts der loadSprites-Routine ein.

;*******************************************************************************
;*** Positioniert alle Sprites
;*******************************************************************************
;*** Übergabe: Bei tileOrder 'steht', auf welchem Feld, welches Puzzleteil
;***           liegt. Da Puzzleteil = Sprite gilt, finden wir so auch das 
;***           jeweilige Sprite.
;*******************************************************************************
;*** Rückgabe: Im jeweiligen SPRITE(n)X & SPRITE(n)Y Register stehen die 
;***           richtigen Koordinaten
;*******************************************************************************
;*** ändert  : A, X, Y, SR
;*******************************************************************************
!zone spriteSetPositions
spriteSetPositions
 ldx #$08                          ;max. 9 Felder
.loop
 ldy tileOrder,X                   ;was befindet sich auf dem Feld?
 bmi .skip                         ;wenn negativ, dann leeres Feld überspringen
 txa                               ;da wir X verändert -> Akku
 pha                               ;und auf dem Stack merken
 tya                               ;Y muss mit 2 multipliziert werden -> Akku
 asl                               ;Akku * 2
 tay                               ;und zurück nach Y
 lda spritePosHelper,X             ;Hilfswert in den Akku holen
 tax                               ;und nach X kopieren
 lda spritePos,X                   ;X-Position fürs aktuelle Feld holen
 sta SPRITE0X,Y                    ;und im richtigen Sprite eintragen
 lda spritePos+1,X                 ;Y-Position fürs aktuelle Feld holen
 sta SPRITE0Y,Y                    ;und im Sprite speichern
 pla                               ;alten X-Wert vom Stack in den Akku
 tax                               ;und Akku nach X kopieren
.skip                              ;Ziel, falls leeres Feld
 dex                               ;nächstes Feld
 bpl .loop                         ;solange positiv nach .loop
 rts                               ;zurück

Zu Beginn holen wir uns die Anzahl der Felder ins X-Register. Da wir von 8 bis 0 durch die Felder gehen, möchte ich nochmal auf den Unterschied zwischen unserer Zählung und der Position in der Liste eingehen. Da ich die Felder von 1-9 nummeriert habe, so wie es die meisten wohl intuitiv machen würden, müssen wir darauf achten, dass die Position im Speicher von 0 bis 8 geht.

tileOrder
 ;Feld       1    2    3    4    5    6    7    8    9
 ;Speicher   0    1    2    3    4    5    6    7    8
 !byte     $00, $01, $02, $03, $04, $05, $06, $07, $ff

Wir finden also den Inhalt von Feld 3 an der Adresse von tileOrder plus 2 oder allgemein ausgedrückt an Adresse von tileOrder + (Feld-Nr. – 1).

Hinter dem .loop holen wir uns den Inhalt des jeweiligen Feldes ins Y-Register. Ist dies negativ, haben wir das leere Feld und müssen keinen Sprite positionieren, also springen wir zum nächsten „cheap label.skip. Wenn der Wert nicht negativ ist, haben wir einen Sprite und müssen dies jetzt positionieren. Als erstes merken wir uns das X-Register auf dem Stack, da wir es gleich verändern und es auch als Schleifenzähler fungiert. Um mit der Y-indizierten Adressierung auf die richtigen Register zuzugreifen, müssen wir den Inhalt von Y noch mit zwei multiplizieren. Dazu einfach Y in den Akku kopieren, den Akku um ein Bit nach links shiften und wieder zurück ins Y-Register kopieren. Wir wollen nicht nochmal rechnen, also holen wir uns über die Hilfstabelle spritePosHelper die richtige Position für den Zugriff auf die Tabelle spritePos, in der ja unsere Koordinaten stehen, in den Akku. Da wir per X-indizierter Adressierung auf die Tabelle spritePos zugreifen, kopieren wir den Akku ins X-Register.
Nun kommt die eigentliche Positionierung, wir holen uns zunächst mit lda spritePos,X die X-Position in den Akku und speichern diese dann mittels sta SPRITE0X,Y im jeweiligen Register für die Sprite-X-Position. Falls euch nicht ganz klar ist, warum wir das Y-Register für die Adressierung mit zwei multipliziert haben, werft noch mal einen Blick auf die Sprite-Register. Anschließend machen wir dasselbe für die Y-Position.
Jetzt noch den auf dem Stack gemerkten Schleifenzähler wieder ins X-Register holen, verringern und die Schleife abarbeiten, bis alle Felder durchgegangen wurden.
Dann haben wir das Ende der Funktion erreicht und kehren zum Aufrufer zurück.

Als letztes müssen wir unsere Funktion jetzt natürlich noch aufrufen. Sucht den Kommentar ;*** Sprites positionieren. Löscht alles bis zum nächsten Kommentar ;*** Hauptfarbe für alle Sprites auf blau setzen. Fügt dann vor diesem Kommentar, den Aufruf unserer neuen Routine ein:

 jsr spriteSetPositions            ;Sprites positionieren

So, wenn ihr das Programm jetzt startet seht ihr…keine Änderung 🙁 . Wozu nun der ganze Aufwand? Wer alles aufmerksam verfolgt hat, dem sollte der Vorteil aufgefallen sein. Wir können die Sprites jetzt einfach durch Veränderung der Tabelle tileOrder beeinflussen.

Vertauscht zum Test einfach mal die Byte-Werte, hinter tileOrder und startet das Programm erneut.

tileOrder
 !byte $ff, $07, $06, $05, $04, $03, $02, $01, $00
Die Sprites haben ihre Position geändert.

Die Sprites haben sich bewegt, aber der Hintergrund ist noch starr. Das sieht natürlich nich so toll aus, aber darum kümmern wir uns im nächsten Beitrag. Sorgt zum Schluß, bitte wieder für die richtige Reihenfolge, der Sprites.


Das wars mal wieder, beim nächsten Mal verschieben wir auch den Hintergrund unserer Puzzleteile.


Schrott!!Naja...Geht so...Ganz gut...SUPER! (7 Bewertungen | Ø 4,71 von 5 | 94,29%)

Loading...


ZurückWeiter

Ein Gedanke zu „Puzzle 004“

  1. Ich arbeite grade deine Anleitung durch und muss sagen, dass es mir riesigen Spaß macht! Wie unterschiedlich die Programmierung doch ist, im Gegensatz zu heutigen Programmiersprachen. Das lässt mich hier sitzen, als würde ich Programmieren noch einmal neu lernen.
    Vielen Dank dafür!

    Eine kleine Anmerkung habe ich aber zu dem Text hier auf dieser Seite…
    Am Ende wo gesagt wird, man solle den Bereich zwischen zwei Kommentaren löschen, stand ich kurz auf dem Schlauch… Ich schreibe die Code-Kommentare nämlich nicht gleich beim programmieren… Ich schreibe einen Tag später meine eigenen und gucke, wie viel hängen geblieben ist… Da ich dadurch deine Kommentare natürlich nicht finden kann, war ich kurz aufgeschmissen. Es ließ sich zwar mit einem kurzen Klick zum vorherigen Beitrag rausbekommen, was du meinst, aber ich war kurz verdutzt.

    Das ist aber die erste Stelle, an der ich kurz innehalten musste. Nochmal vielen Dank für diese tolle Seite hier und ich hoffe, dass es in Zukunft noch neue Beiträge von dir hier gibt
    Robert

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Protected by WP Anti Spam