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

Puzzle 007

Endlich selber puzzeln

C64 Studio

Nun wird es langsam Zeit für etwas Interaktivität, bei unserem Puzzle: Wir kümmern uns jetzt um die Eingabe.
Der Spieler soll ja mit dem Joystick das Puzzle lösen und schon müssen wir wieder eine schwerwiegende 😉 Entscheidung treffen:

Wie versteht der Spieler das Puzzle?

Ihr kennt unser Schiebepuzzle bestimmt schon, hier z. B. eines aus dem CBM prg Studio.

Schiebepuzzle aus dem prg Studio
Puzzle aus dem CBM prg Studio

Hier ist es relativ intuitiv zu verstehen, dass man auf das Teil klickt, dass man bewegen möchte. Ebenso, wie man bei einem echten Schiebepuzzle ein Teil mit seinen Fingern in die Lücke verschiebt.

Unlösbares 15‘er Spiel!

Das obige Puzzle ist übrigens (wie vom Erfinder ursprünglich vorgesehen) nicht lösbar! Für unser Spiel ist das natürlich undenkbar, wir müssen darauf achten, ein lösbares Ausgangsbild zu erzeugen! Darum werden wir uns aber erst im nächsten Beitrag kümmern, hier wollen wir zunächst die Teile mit dem Joystick bewegen und da haben wir unser Problem.

Puzzleteil oder leeres Feld mit dem Joystick bewegen?
Leeres Feld oder Puzzleteil mit dem Joystick bewegen?

Ist man mit dem echten 15‘er-Puzzle vertraut, wird man das Puzzleteil bewegen wollen. Wenn man es allerdings nicht kennt, könnte man auch meinen, man steuert das leere Feld, das sticht schließlich so schön heraus. Bevor wir jetzt mit Kanonen auf Spatzen schießen und das einstellbar machen, treffen wir einfach die Entscheidung und drücken dem Spieler unsere Sichtweise auf.

Ich habe mich für das Bewegen des jeweiligen Puzzleteils, so wie beim realem 15‘er-Puzzlespiel entschieden.

Das Programm anpassen

Für unsere Eingabe brauchen wir noch einige Hilfsvariablen. Fügt also zu den Variablen, am Besten hinter calc16Bit, noch folgendes hinzu:

;*** Wo befindet sich das leere Feld
freetilePos
 !byte $00

;*** Hilfstabelle erlaubte Richtungen je Position
inputHelper
 !byte %00000101, %00001101, %00001001
 !byte %00000111, %00001111, %00001011
 !byte %00000110, %00001110, %00001010

In freetilePos merken wir uns die Position des freien Feldes, damit wir nicht immer nach dem $ff suchen müssen. Wir betrachten für unsere Bewegung immer das freie Feld und machen daran fest welche Bewegungen erlaubt sind. Die erlaubten Richtungen packen wir in die Hilfstabelle inputHelper. Dort findet ihr für jede Position des freien Feldes binär die erlaubten Richtungen. Schaut dafür evtl. noch mal auf die Konstanten JOY_…, dann werdet ihr sehen, dass wenn das freie Feld sich oben links befindet nur die Richtungen RAUF und LINKS erlaubt ist (denkt daran, dass wir die Puzzleteile bewegen und nicht das freie Feld). Der Aufbau der Tabelle ist analog zu tileOrder.

Eingaberoutine

Dann kümmern wir uns mal um die Eingabe und fragen zunächst den Joystick ab. Fügt die folgende Routine am Besten direkt hinter puzzleMain ein.

;*******************************************************************************
;*** Joystickeingabe prüfen
;*** Prüft ob nur eine Richtung gedrückt ist und die Richtung aktuell erlaubt
;*** ist. Wenn beides zutrifft, wird die Eingabe über perfomInput verarbeitet.
;*******************************************************************************
;*** Übergabe: -
;*******************************************************************************
;*** Rückgabe: letzte Eingabe in inputState
;*******************************************************************************
;*** ändert  : A, X, Y, SR, inputState
;*******************************************************************************
!zone checkInput
checkInput
 jsr joystickInput                  ;Aktuellen Joystick-Status holen
 and #%00011111                     ;Nur die unteren 5-BIT sind von Interesse
 eor #%00011111                     ;Da Low-Aktiv, umkehren
 beq .exit                          ;keine Eingabe, dann ENDE
 sta inputState                     ;sonst merken wir uns den angepassten Wert
.loop                               ;prüfen, ob nur eine Richtung gedrückt ist
 lsr                                ;solange nach rechts verschieben, bis C=1
 bcc .loop                          ;wenn C noch 0, weiter verschieben
 bne .exit                          ;sonst ENDE, falls Rest <> 0
 ldx freetilePos                    ;Pos des freien Feldes ins X-Reg.
 lda inputHelper,X                  ;erlaubte Richtungen in den Akku
 and inputState                     ;mit unserer aktuellen Eingabe prüfen 
 beq .exit                          ;wenn die Richtung verboten ist, ENDE
 jsr performInput                   ;hier ist alles OK, also verarbeiten
.exit
 rts                                ;zurück

Gleich zu Anfang von checkInput, springen wir in unsere bereits vorhandene Routine joystickInput, zum Auslesen des Joystickstatus. Das Ergebnis steht wieder im Akku und wird auch unter inputState gespeichert. Als nächste nehmen wir eine kleine Anpassung am Joystickstatus vor. Die benötigten Register $dc00 (CIA1_A / Joy-2) & $dc01 (CIA1_B / Joy-1) sind low aktiv, d. h. die Bits stehen auf 1, wenn der Joystick nicht betätigt wird und wechseln auf 0, wenn eine Richtung gedrückt ist. Da dies etwas unbequem ist, kehren wir das mit einem eor einfach um, nachdem wir zur Sicherheit die oberen drei Bits ausgeblendet haben. Ist das Ergebnis null, dann wird der Joystick gerade nicht benutzt und wir brauchen nichts weiter zu prüfen, sonst merken wir uns den korrigierten Zustand wieder in inputState. Als nächstes wollen wir sichergehen, dass nur eine Richtung gedrückt wurde. Dazu verschieben wir den Akku-Inhalt (dort steht auch die korrigierte Eingabe) solange nach rechts, bis wir eine 1 im Carry-Flag haben (eine muss vorhanden sein, sonst würden wir diese Zeile nicht erreichen s. o.). Da lsr auch das Zero-Flag beeinflusst, können wir dann mit einem bne prüfen ob der Rest Null ist (also keine weitere Richtung vorliegt). Wenn nur eine einzelne Richtung vorliegt, holen wir uns die Position des freien Feldes von freetilePos ins X-Reg., um damit aus der Tabelle inputHelper die erlaubten Richtungen zu holen. Diese prüfen wir dann mit der aktuellen Eingabe und nur wenn die gerade betätigte Richtung wirklich erlaubt ist, springen wir zur Verarbeitung nach performInput.

Die Eingabe umsetzen

Jetzt müssen wir noch unsere Eingabe in die Tat umsetzen. Dies machen wir in der Funktion performInput, die wir einfach hinter die Funktion von eben schreiben.

;*******************************************************************************
;*** Joystickeingabe verarbeiten
;*** Vertauscht das freie Feld und das entsprechende Puzzleteil.
;*** Anschließend wird alles erneut gezeichnet.
;*******************************************************************************
;*** Übergabe: Akku = korrigierte Eingabe
;*******************************************************************************
;*** Rückgabe: Getauschte Puzzleteile in tileOrder
;*******************************************************************************
;*** ändert  : A, X, Y, SR, freetilePos, tileOrder
;*******************************************************************************
!zone performInput
performInput
 lsr                                ;Per Bitverschiebung die Eingabe suchen
 bcc .down                          ;wenn C=0, dann runter prüfen
 ;*** Joystick rauf
 lda freetilePos                    ;akt. Pos. des leeren Feldes in den Akku
 clc                                ;C=0 für ADC
 adc #$03                           ;+3, für das Feld unter dem freien 
 tay                                ;im Y-Reg. merken
 lda tileOrder,Y                    ;Puzzleteil in den Akku
 tax                                ;im X-Reg. merken
 lda #$ff                           ;Wert für freies Feld in den Akku
 sta tileOrder,Y                    ;und an errechneter Pos. speichern
 txa                                ;gemerktes Puzzleteil in den Akku
 sta tileOrder-3,Y                  ;und ins bisher freie Feld kopieren
 sty freetilePos                    ;Pos. des leeren Feldes nach freetilepos:
 jmp .refresh                       ;und alles neu zeichnen
.down                               ;hier ist fast alles wie eben bei Jostick rauf
 lsr
 bcc .left                          ;nur dass wir ggf. 'nach links' prüfen
 ;*** Joystick runter
 lda freetilePos
 sec
 sbc #$03                           ;und jetzt -3 rechnen, für das über dem
 tay                                ;leeren Feld liegende Puzzleteil
 lda tileOrder,Y
 tax
 lda #$ff
 sta tileOrder,Y
 txa
 sta tileOrder+3,Y                  ;auch hier das andere Vorzeichen beachten
 sty freetilePos
 jmp .refresh
.left
 lsr
 bcc .right
 ;*** Joystick links    
 ldy freetilePos                    ;hier vertauschen wir einfach das leere
 iny                                ;Feld mit dem Teil rechts
 lda tileOrder,Y
 tax
 lda #$ff
 sta tileOrder,Y
 txa
 sta tileOrder-1,Y
 sty freetilePos
 jmp .refresh
.right                              ;wenn es keine der anderen Richtungen war,
 ;*** Joystick rechts               ;dann bleibt nur rechts übrig
 ldy freetilePos                    ;das freie Feld mit dem Teil links tauschen
 dey                               
 lda tileOrder,Y
 tax
 lda #$ff
 sta tileOrder,Y
 txa
 sta tileOrder+1,Y
 sty freetilePos
.refresh
 jsr spriteSetPositions             ;Sprites positionieren
 jsr drawTiles                      ;Hintergründe zeichnen
 rts                                ;zurück

Wir verschieben den Akku-Inhalt nach rechts und kontrollieren das Carry-Flag. Wenn dort eine 1 auftaucht, wurde der Joystick RAUF gedrückt und wir verarbeiten die Richtung-Oben, falls eine 0 im Carry-Flag steht, springen wir zur Prüfung für RUNTER. Werft ggf. nochmal einen Blick auf die Tastaturmatrix und dort natürlich speziell auf die Joysticks, falls ihr mit der bitweisen Prüfung hadert. Wir schauen uns jetzt mal an, was passiert, wenn der Joystick RAUF gedrückt wurde, in unserem Beispiel steht das freie Feld in der Mitte (also auf Position 5). Wenn wir RAUF gedrückt haben, müssen wir jetzt also das Puzzleteil unter dem freien Feld finden. Dazu holen wir uns die freetilePos in den Akku und addieren einfach zur Position des freien Feldes 3, das könnt ihr ganz einfach abzählen. Für den Zugriff auf tileOrder, kopieren wir den Wert dann ins Y-Register. Nun merken wir uns das Puzzelteil im X-Register, überschreiben es dann in tileOrder mit $ff und holen uns das gemerkte Teil von X in den Akku. Das speichern wir dann an der alten Position des freien Feldes, darauf greifen wir mit tileOrder-3 zu (da im Y-Reg. die neue Position steht, müssen wir ja wieder drei Felder zurück) und legen dort das Puzzleteil ab. Im Y-Register steht ja die neue Position des freien Feldes, diese speichern wir natürlich noch unter freetilePos ab. Zum Schluß springen wir dann zum Refresh. Dort zeichnen wir einfach alles neu. Das ist hier OK, aber bei aufwendigeren Szenen sollten wir uns darauf beschränken nur das zu zeichnen, was sich wirklich geändert hat.
Die anderen Richtungen funktionieren ähnlich, die Funktionsweise sollte euch durch die Kommentare im Sourcecode klar werden.

Wir sind nun fast durch, aber damit freetilePos zu Beginn richtig initialisiert ist, sollten wir vor das rts in, shuffleTiles noch einige Zeilen (gelb hervorgehoben) hinzufügen.

shuffleTiles
...
 bpl .loop1                         ;solange noch eins da ist
 ldx #$09                           ;Wir beginnen mit 9, da...
.loop2
 dex                                ;...direkt am Schleifenstart der Wert verringert wird.
 lda tileOrder,X                    ;Feld-Nr. holen
 bpl .loop2                         ;Falls NICHT $ff (leeres Feld), nächstes testen
 stx freetilePos                    ;Position des leeren Feldes merken
 rts                                ;zurück

Wir durchsuchen hier tileOrder einfach nur nach dem $ff und merken uns die Position in freetilePos.

Fügt jetz noch den Aufruf für checkInput direkt hinter puzzleMainLoop ein:

puzzleMainLoop                      ;Start der Endlosschleife
 jsr checkInput                     ;Joysticks prüfen & Eingaben verarbeiten
 jmp puzzleMainLoop                 ;Endlosschleife

Endlich ist es geschafft. Übersetzt das Programm und startet es… Ooops, ganz wie gewünscht klappt es noch nicht, wir sind aber nah dran. Ihr habt ja recht, nah dran ist ein Dessousladen bei dem die Schaufensterscheibe vergessen wurde. Es bewegt sich mehr als gewünscht, wer möchte kann ja mal versuchen es in den Griff zu bekommen. Ich werde meine Lösung im nächsten Beitrag zeigen.


Schrott!!Naja...Geht so...Ganz gut...SUPER! (5 Bewertungen | Ø 5,00 von 5 | 100,00%)

Loading...


ZurückWeiter

5 Gedanken zu „Puzzle 007“

  1. Ich hab noch einen Verbesserungsvorschlag für performInput! :0D
    ist das tax und txa nicht überflüssig, wenn man zuerst das Sprite an die leere Stelle verschiebt und dann die leere Stelle zur aktuellen Stelle?
    Also so, für right:

    ldy freetilePos
    dey
    lda tileOrder, Y
    sta tileOrder+1,Y
    lda #$ff
    sta tileOrder,Y
    sty freetilePos

  2. Ich hab um das Problem der automatischen wiederholten Eingabe zu lösen, folgendes gemacht:

    1. Die Speicherstelle inputState habe ich am Anfang mit $ff initialisiert
    2. Mein checkInput sieht jetzt folgendermaßen aus:

    lda inputState
    tax
    jsr joystickInput
    txa
    ora #%11100000
    cmp #$ff
    bne .exit

    lda inputState
    and #%00011111
    eor #%00011111
    beq .exit
    sta inputState
    .loop
    lsr
    bcc .loop
    bne .exit
    ldx freetilePos
    lda inputHelper, X
    and inputState
    beq .exit
    jsr performInput
    .exit
    rts

    Ich hole also erst den alten inputState in den Akku,
    speicher den ins X-Register,
    lade die neue Eingabe nach inputState,
    hole mir meine alte Eingabe wieder,
    sorge mit dem ora dafür, dass wenn nichts gedrückt wurde #$ff im Akku steht,
    vergleiche den Akku mit $ff,
    springe zu .exit, wenn der letzte inputState eine Eingabe enthielt.

    Wenn der Joystick vor der aktuellen Bewegung jedoch nicht geneigt und nicht gefeuert wurde, lade ich die aktuelle Bewegung wieder in den Akku und alles funktioniert wieder wie vorher. :0D

  3. Ich habe es gelöst! Ich habe den Joystickzustand in einer Variablen gespeichert und es wird erst ein Tile bewegt wenn der Joystick wieder losgelassen wird. Dadurch bewegt sich nur 1 Tile statt der ganzen Reihe:

    checkinput:
    jsr joystickInput: ;Aktuellen Joystick-Status holen
    sta joysticklaststate:
    and #%00011111 ;Nur die unteren 5-BIT sind von Interesse
    eor #%00011111 ;Da Low-Aktiv, umkehren
    beq checkinput_exit: ;keine Eingabe, dann ENDE
    sta inputState: ;sonst merken wir uns den angepassten Wert

    ciloop: ;prüfen, ob nur eine Richtung gedrückt ist
    lsr ;solange nach rechts verschieben, bis C=1
    bcc ciloop: ;wenn C noch 0, weiter verschieben
    bne checkinput_exit: ;sonst ENDE, falls Rest 0
    ldx freetilepos: ;Pos des freien Feldes ins X-Reg.
    lda inputhelper:,X ;erlaubte Richtungen in den Akku
    and inputState: ;mit unserer aktuellen Eingabe prüfen
    beq checkinput_exit: ;wenn die Richtung verboten ist, ENDE

    tax
    waitjoystickrelease:
    jsr joystickInput:
    cmp joysticklaststate:
    beq waitjoystickrelease: ; go on if joystick is no more moved
    txa

    jsr performinput: ;hier ist alles OK, also verarbeiten

    checkinput_exit:
    rts ;zurück

    joysticklaststate:
    !byte $00

  4. Hallo,
    Der Build zeigt 2 Probleme an:
    Invalid Address Mode “sta tileorder:+3,y”
    Invalid Address Mode “sta tileorder:+1,y”
    Eine Idee wo die herkommen? Danke. 🙂

Schreibe einen Kommentar

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

Protected by WP Anti Spam