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

Puzzle 008

Lauter Lösungen

C64 Studio

Im letzten Beitrag haben wir die Möglichkeit zur Bewegung der Puzzleteile eingebaut. Dabei kam es aber zum Problem: Die Bewegung ging so schnell vonstatten, dass wir häufig zwei Teile gleichzeitig bewegt haben. Wie können wir das Problem jetzt lösen?

Die Joystickabfrage optimieren

Wir könnten z. B. eine Verzögerung einbauen, so dass der Spieler die Möglichkeit erhält den Joystick ggf. loszulassen. Ich möchte aber einen anderen Ansatz verfolgen, wir lassen einfach nur einen Schritt zur Zeit zu.

Fügt die unten markierten Zeilen zu checkInput hinzu:

!zone checkInput
checkInput
 ldx inputState                     ;letzte Eingabe ins X-Register
 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
 cpx inputState                     ;hat sich die Eingabe geändert?
 beq .exit                          ;wenn, nein -> ENDE

Direkt zu Beginn holen wir uns nun die letzte Eingabe ins X-Register. Nach dem Prüfen der aktuellen Eingabe, kontrollieren wir ganz zum Schluß, ob sich die Eingabe geändert hat. Dazu vergleichen wir X mit der aktuellen Eingabe, falls sie sich nicht geändert hat, springen wir direkt zum Ende der Routine, anderenfalls verarbeiten wir die Eingabe.
Durch diese Änderung müssen wir jetzt den Joystick jedes mal wieder loslassen oder in eine andere Richtung drücken, damit die Routine auf eine Eingabe reagiert.
Wenn ihr das Programm nun startet, könnt ihr die Puzzleteile endlich kontrolliert bewegen.

Das Mischen optimieren

Das nächste Problem ist unsere shuffleTiles-Routine. Dort wurden die Puzzleteile reinzufällig verteilt, dabei kann es spätestens bei größeren Spielfeldern zu unlösbaren Puzzles kommen. Dies müssen wir natürlich verhindern! Also werden wir jetzt das Puzzle aus der gelösten Position per zufälliger virtueller Joystick-Eingaben durcheinanderwürfeln.

Ihr könnt nun die bisherige Funktion shuffleTiles komplett gegen diese ersetzen:

;*******************************************************************************
;*** Die Puzzleteile zufällig verteilen
;*******************************************************************************
;*** Übergabe: -
;*******************************************************************************
;*** Rückgabe: Neue Position der Puzzleteile in der Tabelle tileOrder
;*******************************************************************************
;*** ändert  : A, X, Y, SR
;*******************************************************************************
!zone shuffleTiles
shuffleTiles
 ldx #$20                           ;Anzahl der Schritte, je höher je schwerer
.loop
 txa                                ;X auf den 
 pha                                ;Stack
.loop1
 jsr rndTimer                       ;Zufallszahl holen
 and #%00000011                     ;wir brauchen nur die beiden unteren BITs
 tay                                ;ins Y-Register
 lda #JOY_FIRE                      ;nur BIT 4 im Akku setzen 
.loop2
 lsr                                ;Akku bitweise nach rechts
 dey                                ;Y verringern
 bpl .loop2                         ;solange positiv, weiter verschieben
 ldx freetilePos                    ;Pos des freien Feldes ins X-Reg.
 and inputHelper,X                  ;erlaubte Richtungen prüfen
 beq .loop1                         ;wenn die Richtung verboten ist, nochmal
 jsr performInput                   ;sonst, Eingabe verarbeiten
 pla                                ;gemerktes X
 tax                                ;wieder vom Stack holen
 dex                                ;verringern
 bne .loop                          ;und ggf. weiter 'verwürfeln'
 rts                                ;zurück

Am Anfang legen wir im X-Register die Anzahl der Schritte fest, die wir fürs Verwürfeln nutzen möchten. Je höher ihr diesen Wert setzt, desto länger dauert das Mischen und um so schwieriger wird es. Da wir X gleich für etwas Anderes benötigen, merken wir uns den Schleifenzähler auf dem Stack.
Dann holen wir uns eine Zufallszahl. Da wir nur die vier Hauptrichtungen des Joysticks verarbeiten, brauchen wir auch nur die unteren beiden Bits. Dann kopieren wir die Zufallszahl aus dem Akku ins Y-Register und füllen den Akku mit dem Wert für den Feuerknopf (%00010000). Um nun an unsere Joystick-Richtung zu gelangen, verschieben wir den Akku um die ermittelte Zufallszahl nach rechts. Schaut ggf. nochmal auf die JOY_…-Konstanten, dann werdet ihr sehen, dass die Richtungen rechts vom Feuerknopf liegen.
Sobald wir unsere zufällige Richtung haben, müssen wir natürlich noch prüfen, ob diese überhaupt erlaubt ist. Dazu laden wir die Position des freien Teils ins X-Register und prüfen mit inputHelper, ob die Richtung aktuell erlaubt ist. Falls nicht holen wir uns eine neue Richtung, anderenfalls lassen wir die Eingabe bei performInput verarbeiten.
Am Ende holen wir unseren Schleifenzähler wieder vom Stack ins X-Register, verringern X und wiederholen alles, bis X den Wert 0 annimmt.

Damit alles korrekt funktioniert, müsst ihr noch die Variablen korrekt initialisieren. Dies macht bitte zunächst direkt im Source.
Kontrolliert, ob eure Werte, wiefolgt aussehen:

;*** Wo befindet sich das leere Feld
freetilePos
 !byte $08
;*** 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|
                     ;-------

In freetilePos muss also eine 8 stehen und unter tileOrder müssen alle Puzzleteile aufsteigend gespeichert sein, so wie es bei der korrekten Lösung der Fall ist.

Startet ihr das Programm, dann sollten die Puzzleteile nun lösbar verteilt sein. Da wir performInput verwenden, können wir das Mischen sogar verfolgen, es sieht aber noch nicht besonders schick aus (irgendwie fehlen unsere Sprites). Wir müssen in puzzleMain nur die Zeile jsr shuffleTiles vor jsr loadSprites verschieben, damit werden die Sprites initialisiert und wir sehen jetzt beim Mischen auch die Sprites. Wer möchte kann dies natürlich abschalten, wenn es für den Spieler nicht sichtbar sein soll oder verlangsamen, damit der Spieler das Mischen besser verfolgen kann.

Etwas zufälligere Zufallszahlen 😉

Mir ist bei meinen Tests aufgefallen, dass die Verteilung der Puzzleteile nicht immer sehr vorteilhaft ist. Das liegt evtl. daran, dass wir in shuffleTiles sehr schnell Zufallszahlen ermitteln (vgl. Zufallszahlen in Assembler). Ich möchte daher eine eigene Quelle für die Zufallszahlen verwenden.

Fügt in direkt hinter main die beiden markierten Zeilen ein:

main
 lda VICRASTERROWPOS                ;aktuelle Rasterzeile für rndSeed holen
 sta rndSeed                        ;und unter rndSeed speichern
 jsr showScreen_Title               ;Startbildschirm anzeigen
 jsr puzzleMain                     ;das Puzzle starten
 rts                                ;Zurück zum BASIC

VICRASTERROWPOS ist eine neue Konstante, die ihr oben bei den anderen VIC-Konstanten einfügen solltet.

VICRASTERROWPOS     = $d012         ;(18) Aktuelle Rasterzeile lesen oder setzen

Wie ihr seht, hat sie den Wert $d012, ihr könnt also auch direkt lda $d012 verwenden, falls ihr auf die Konstante verzichten wollt.

Unsere Quelle merken wir uns bei rndSeed, für die spätere Verwendung. Dafür benötigen wir eine Variable für ein Byte, fügt diese am Besten hinter calc16Bit ein.

;*** Basis für die Zufallszahlen
rndSeed
 !byte $00

Jetzt müssen wir noch rndTimer anpassen. Fügt dort gleich vor dem rts diese zwei Anweisung ein:

 eor rndSeed                        ;noch mit unserem Quellwert verknüpfen
 sta rndSeed                        ;und Rückgabe als neue Quelle speichern

Wir verknüpfen unsere Zufallszahl noch mit unserer Quelle und speichern die eben ermittelte Zufallszahl als neue Quelle.

Prüfen, ob das Puzzle gelöst wurde

Ein Problem haben wir allerdings immer noch, wir müssen verhindern, dass beim Mischen zufällig ein bereits gelöstest Puzzle herauskommt. Wir benötigen aber sowieso noch eine Routine, die das prüft. Lasst uns doch direkt die neue Funktion checkPuzzle, hinter puzzleMain einfügen.

;*******************************************************************************
;*** Prüfen, ob das Puzzle gelöst wurde
;*******************************************************************************
;*** Übergabe: -
;*******************************************************************************
;*** Rückgabe: A = 0 (NICHT gelöst)
;***           A = 1 (gelöst)
;*******************************************************************************
;*** ändert  : A, X, SR
;*******************************************************************************
!zone checkPuzzle
checkpPuzzle
 ldx tileOrder+8                    ;steht das leere Feld rechts unten?
 bpl .exit                          ;falls nein zum Exit springen
 ldx #$07                           ;sonst die restlichen Felder prüfen
.loop
 txa                                ;X für Vergleich in den Akku
 cmp tileOrder,X                    ;Akku mit Puzzle vergleichen
 bne .exit                          ;wenn NICHT identisch zum EXIT
 dex                                ;nächstes Puzzleteil
 bne .loop                          ;prüfen, solange > 0
 lda #$01                           ;hier ist alles OK, also 1 in den Akku
 rts                                ;zurück
.exit
 lda #$00                           ;nicht gelöst, 0 in den Akku
 rts                                ;zurück

Als Erstes prüfen wir, ob das freie Feld rechts unten steht, nur dann müssen wir überhaupt weiter testen. Wenn das freie Feld sich an der richtigen Position befindet, kontrollieren wir, ob alle anderen Felder aufsteigend sortiert sind. Dazu holen wir uns die Anzahl der restlichen Felder (#$07, da 0 basierend) ins X-Register. Wir kopieren für die Prüfung X in den Akku und vergleichen jetzt ganz einfach, ob an der über X angegebenen Position in tileOrder der selbe Wert wie im X-Register (bzw. Akku) steht. Sind die Werte unterschiedlich, brechen wir ab, sonst verringern wir X und prüfen weiter rückwärts bis X = 0 ist. Das letzte Puzzleteil brauchen wir natürlich nicht mehr zu prüfen, wenn das leere Feld korrekt platziert ist und alle anderen Teile auch stimmen, muss zwangläufig das letzte Puzzleteil auch an der richtigen Position stehen.
Ist das Puzzle gelöst, gibt die Funktion eine 1 im Akku zurück, sonst eine 0.

Damit wir verhindern, dass unser Mischen dem Spieler ein gelöstest Puzzle präsentiert, bauen wir die neue Funktion checkPuzzle dort gleich mal ein.

Fügt also zu shuffleTiles direkt vor dem rts am Ende, noch die folgenden beiden Zeilen ein:

 jsr checkPuzzle                    ;prüfen ob das Puzzle zufällig gelöst wurde
 bne shuffleTiles                   ;falls gelöst, nochmal von vorne

Jetzt starten wir die Routine einfach noch mal neu, falls das Puzzle gleich gelöst ist.

Wer möchte, kann die Prüfung, ob das Puzzle gelöst wurde, ja noch in checkInput einbauen. Ändern wir doch die Rahmenfarbe, sobald das Puzzle fertig ist. Schreibt einfach folgende Zeilen hinter das jsr performInput in checkInput.

 jsr checkPuzzle                    ;nach jedem Zug prüfen, ob es gelöst wurde
 beq .exit                          ;steht eine 0 im Akku, dann nicht gelöst
 dec VICBORDERCOLOR                 ;sonst sind wir fertig und verändern erstmal die Rahmenfarbe
Anzeigen, dass das Puzzle gelöst wurde.

Natürlich ist das nur eine kleine optische Anzeige. Das Spiel läuft erstmal einfach weiter.


Damit sind wir am Ende angelangt. Das Puzzle schreitet langsam voran, aber ein paar Sachen fehlen natürlich noch. Wir sollten den Spieler durch eine Zugbeschränkung oder einen Timer etwas unter Druck setzen. Wenn wir nett sind, könnten wir z. B. noch Punkte fürs Lösen des Puzzles vergeben und wir müssen natürlich auch ein Scheitern des Spielers behandeln.


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

Loading...


ZurückWeiter

Schreibe einen Kommentar

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

Protected by WP Anti Spam