Endlich selber puzzeln
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.
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.
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.
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.
Schritt 7 - Puzzle 007
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
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
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
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. 🙂
Jup, wieder ein Fehler des CBM prg Studio 3.0.0!
Du kannst das, bis zum offiziellen Erscheinen der Version 3.1.0, durch eine Hilfskonstante umgehen.
Aus
sta tileorder:+3,y
muss dann
dummy=tileorder:+3
sta dummy,y
werden.