Memory

posted

2026-02-22

updated

2026-02-22
 
Eine simple digitale Version vom Memory.
Zu sehen sind eine verdeckte Karten und die Spieler müssen Pärchen finden. 
Das Projekt hatte auf die Verbesserung des Inputhandling abgezielt,
dabei bin ich allerdings noch einem weiterem Problem gegenübergetreten.
Nach einer gründlichen Überlegung, habe ich dieses Konzept wieder entfernt, lasse es aber in meiner Toolbox für nähere Betrachtung. 


 
Dieses Projekt basiert nur auf dem Basis Framework. 
Neue Klassen und Funktionen 
Branching Response 
Diese Helferklasse ermöglicht es Rückgabewerte von Funktionen mit etwas mehr Bedeutung zu schmücken und auf diese zu reagieren. 
export class BranchingResponse<RESPONSE, STATES, PARAMETERS> {
    public response: RESPONSE | null = null;
    public constructor(
        private state: STATES,
        private params: PARAMETERS
    ) { }
    public do_on_match(callback: BranchingResponseCallback<RESPONSE, PARAMETERS>, state: STATES): this {
        if (state === this.state) return this;
        this.response = callback(this.params, this.response);
        return this;
    };   
}
 
So kann eine Funktion in mehreren Zuständen resultieren und je nach dem Ergebnis soll etwas anderes passieren.
Als Beispiel diese Funktion: 

/// takes a number and assures it is even,
/// only works on positive numbers
function make_even(value:number) : BranchingResponse<number, function make_even(value: number): BranchingResponse<number, "even" | "odd" | "negative", number> {
    if (value < 0) return new BranchingResponse("negative", value, value);
    if (value % 2 === 0) return new BranchingResponse("even", value, value);
    return new BranchingResponse("odd", value + 1, value + 1);
}
 
Verwendet die BranchingResponse, um zu kommunizieren, dass die Funktion an dieser Stelle nicht richtig ausgeführt werden konnte.
In gewisser Weise, agiert die BranchingRespone es hier wie eine geworfene Exception. Im aufrufenden Code sieht es dann möglicherweise so aus:
 
// will return 0 if the input was negative
function make_even_or_zero(x: number): number {
    return make_even(x)
    .do_on_match(() => 0, 'negative')
    .response;
}
// will negate the value and add one to make the value even 
function make_even_forced(x: number): number {
    return make_even(x)
        .do_on_match((x) => make_even(-x).response, 'negative')
        .response;
}
 
Hier wird also dem Aufrufer die Möglichkeit gegeben in gewissen Zuständen das Ergebnis nochmal anzupassen.
 
Natürlich kann dies auch wie folgt ausgedrückt werden:
 
function make_even(value: number): number {
    if (value < 0) throw new Error("Not allowed");
    if (value % 2 === 0) return value + 1;
    return value;
}
function make_even_or_zero(x: number): number {
    if (x < 0) return 0;
    return make_even(x);
}
function make_even_forced(x: number): number {
    if (x < 0) return make_even(-x);
    return make_even(x);
}
 
Vorerst habe ich diese Vorgehensweise in diesem Projekt wieder herausgenommen, da die anonymen Funktionen in den "match"-Methoden zu groß wurden, als das es an der Stelle noch passend schien.
 
Assert Never
 
Eine kleine typescript-utility, die vergessene Code-Pfade zur Compilezeit aufzeigen soll.
 
export function assert_never(variable: never): never {
    return variable;
}
 
Angewandt in bzw. nach switch Statements führt diese Funktion zu Compilerfehlern, wenn nicht alle Bedingungen abgefragt wurden.
Für diese Funktion allein gibt es bereits ein npm-package.
 
Herangehensweise
 
Dieses Projekt hat sich etwas in die länge gezogen.
Mein Experiment mit dem "BranchingResponse" ist ja leider etwas fehlgeschlagen und somit waren die Fortschritte in diesem Projekt beschränkt.
 
Nachdem ich gemerkt hatte, dass ich mich bei meinem ersten Ansatz vertan hab, habe ich den Rest des Project etwas beschleunigt.
Leider war dadurch wenig Interessantes in dem Project zu erkunden.
 
Einige Tage später habe ich mir den Code allerdings noch einmal angeschaut und ein paar Verbesserungen vorgenommen.
 
So habe ich Beispielsweise ein (2D) CameraModel abstrahieren können,
welches eventuell in späteren Projekten wieder als Grundlage zum Einsatz kommen könnte.
 
export class CameraModel extends Model<ModelCollection>{
    // Center of the camera
    public center = new Vector2(0, 0);

    /**
     * 
     * @param shape 
     *  The object to map to the "camera"-coordinates
     * @return 
     *  The transformed object
     */
    public transform(shape: Vector2 | Rect): Vector2 | Rect {
        // ...
    }

    /**
     * 
     * @param shape 
     *  The object to map to the "world"-coordinates
     * @return 
     *  The transformed object
     */
    public reverse_transform(shape: Vector2 | Rect): Vector2 | Rect {
        // ...
    }
}
 
Zudem bin ich noch dazu gekommen den eigentlichen Grund für dieses Projekts als ein Erfolg zu verbuchen, als ich mit der Clickevent-Behandlung eine gewisse Abstraktion geschafft habe. 

export interface UserInterfaceAdaptable {
    collider: Rect;
    is_clickable: boolean;
}

export class MemoryCardModel extends Model<ModelCollection> implements UserInterfaceAdaptable {
...
}
 
Kurz aber knackig kann nutze ich das oben gegebene Interface, um Interface Handlungen entgegenzunehmen, unklar bin ich mir lediglich, ob die Reaktion darauf in dem Model, oder in dem Controller verankert sein sollte.
 
In diesem Projekt, habe ich es über den Controller probiert,
das schien mir aber für eine größere Anzahl and Beispielsweise Menu-Elementen nicht sinnvoll.
 
Fazit
 
Im ganzen bin ich nicht zufrieden, wie dieses Projekt gelaufen ist.
Durch zu viele Unterbrechungen hatte ich keinen sauberen Abschluss und erst spät Ergebnisse, die mich weitergebracht haben.
 
Ich muss bei zukünftigen Projekten vermutlich auf einen noch stärker reduzierten Umfang achten.
 
Neue Ideen
 

Releases

Comments

captcha