Skip to content

Expert review

K5: Object Oriented Programming

Encapsulation

1 Kai

Hieronder is een constructor waarin we alleen privé variabele gebruiken.

constructor(image, size, x, y, tileNumber) {
        this.#image = image;
        this.#size = size;
        this.#x = x;
        this.#y = y;
        this.#tileNumber = tileNumber
        this.#visible = true;
    }

In de class waarin de constructor zich bevindt, zitten ook meerdere methods net zoals deze twee.

get x() {
        return this.#x;
    }

    set x(value) {
        this.#x = value;
    }

Omdat de variabele in de constructor privé zijn moet je met deze functies respectievelijk de variabelen callen en veranderen.

Dit process volgen we zodat collegas code die ze zelf niet hoeven aan te passen, ook niet kunnen aanpassen. Zo wordt het schrijven van code hieromheen minder ingewikkeld.

In de code hieronder kan je zien dat de tileNumber via de constructor (hierboven) van de Tile class word aangemaakt.

class NonMovingTile extends Tile {
    constructor(image, size, x, y, tileNumber) {
        super(image, size, x, y, tileNumber);

        this.tileNumber = 0
    }
}

2 Kai

In dit voorbeeld gebruiken we de class “Boss”, dit is de enemy die we aanvallen, alleen heb je daarvoor dus niet alle code nodig die in de class staat. in de constructor en methode hieronder kan je zien dat de this.#image gecalled word, en wordt gebruikt in de displayBoss methode om de boss te displayen.

#scale = mobileScale;

constructor(gameManager, bossName) {
        this.#image = gameManager.getImage(bossName);
        this.matchContainers();
        this.chooseBossWeapon();
}

displayBoss() {
    this.#innerContainer.innerHTML = '';

    this.#innerContainer.style.transform = `scale(${this.#scale}) translate(${this.#x}px, ${this.#y}px)`;

    const bossImgElement = document.createElement('img');
    bossImgElement.src = this.#image.canvas.toDataURL();
    bossImgElement.id = 'bossImage';

    this.#innerContainer.appendChild(bossImgElement);
    this.#container.appendChild(canvas.elt);
}

De programeur die de boss wilt veranderen of een boss will callen hoeft vervolgens alleen te weten wat hij moet callen, in dit geval de gamemanager die die aanmaakt en de naam van de boss die in de assetmanager staat. vervolgens kan hij die callen en hoeft hij voor de rest geen veranderingen aan de code te maken.

function preload() {
    mobileScale = windowHeight / windowWidth / 2
    new GameManager();
    boss = new Boss(gameManager,"Boss1");
    // Andere code
}

Hierboven zie je een voorbeeld van hoe dit allemaal te werking gaat voor de programmeur

1 Danny

Om de eigenschappen van objecten te beveiligen maken we ze private door er een hash (#) voor te zetten

class TileGrid {
    #tiles;
    #width;
    #height;
    #firstTile = true;
    #timerDefault = 60;
    #timer = this.#timerDefault;
    #turnUsed = false;
    #turnEnded = false;
}

2 Danny

In het voorbeeld hieronder is te zien dat we getters en setters gebruiken om de eigenschappen op een veilige manier te bewerken.

    getTileNumber() {
        return this.#tileNumber;
    }

    setTileNumber(newTileNumber) {
        this.#tileNumber = newTileNumber;
    }

Abstraction

1 Kai

We werken met abstraction om het makkelijker te maken tijdens het codeer process.

{
    this.getTileAtGridIndex(gridXPosition, gridYPosition);
}

    getTileAtGridIndex(x, y) {
        if (x < 0 || x >= this.#width || y < 0 || y >= this.#height) {
            throw new Error("index outside of bounds of grid!");
        }

        return this.#tiles[x][y];
    }

De code die hierboven staat vermakkelijkt het process van het zoeken naar een tile, omdat de codeur alleen the this.getTileAtGridIndex hoeft te gebruiken, en de methode vervolgens al het werk doet. Je hoeft alleen te weten hoe de methode call werkt, en de methode doet de rest zelf.

2 Kai

Hetzelfde geld voor de code die hieronder aan wordt gegeven.

tileGrid.checkClickedOnTileGrid(firstTile.x, firstTile.y)

Deze code verwijst naar het stuk hieronder.

checkClickedOnTileGrid(targetX, targetY) {
        this.targetX = targetX;
        this.targetY = targetY;

        for (let x = 0; x < this.#width; x++) {
            for (let y = 0; y < this.#height; y++) {
                let tile = this.#tiles[x][y]
                if (this.#firstTile && tile.x == this.targetX && tile.y == this.targetY) {
                        this.firstObjectX = tile.x;
                        this.firstObjectY = tile.y;
                        this.#firstTile = false;
                        break;
                }
                if (!this.#firstTile && tile.x == this.targetX && tile.y == this.targetY) {
                        this.secondObjectX = tile.x;
                        this.secondObjectY = tile.y;
                        this.#firstTile = true;

                        this.moveObject();
                }
            }
        }
    }

Omdat de codeur alleen wil weten of de tiles verplaats kunnen worden en niet hoeft te weten hoe die dit moet doen, is het handiger om dit in een methode te verwerken, waardoor de codeur alleen hoeft te weten hoe hij/zij deze methode moet callen om het te laten werken.

Zou je dit niet doen dan moet je steeds die berekening schrijven en dan heb je al snel te veel code.

this.targetX = targetX;
this.targetY = targetY;

for (let x = 0; x < this.#width; x++) {
    for (let y = 0; y < this.#height; y++) {
        let tile = this.#tiles[x][y]
        if (this.#firstTile && tile.x == this.targetX && tile.y == this.targetY) {
            this.firstObjectX = tile.x;
            this.firstObjectY = tile.y;
            this.#firstTile = false;
            break;
        }
        if (!this.#firstTile && tile.x == this.targetX && tile.y == this.targetY) {
            this.secondObjectX = tile.x;
            this.secondObjectY = tile.y;
            this.#firstTile = true;

            this.moveObject();
        }
    }
}

Dit zou je dan voor elke soort click/swipe neer moeten zetten, in plaats van alleen de functie met de 2 parameters die je meegeeft.

1 Danny

Dit voorbeeld bevindt zich in de TileGrid class, op deze manier kan zonder problemen de correcte tile worden aangeroepen/aangepast. Je hoeft niet te weten welke tile het is en wat de eigenschappen van de tile zijn, de positie alleen is genoeg om de tile aan te roepen.

    getTileAtPosition(position) {
        const gridXPosition = Math.floor((position.x - windowWidth / 2 + canvasWidth / 2) / this.tileSize);
        const gridYPosition = Math.floor((position.y - (windowHeight - canvasHeight)) / this.tileSize);

        return this.getTileAtGridIndex(gridXPosition, gridYPosition);
    }

2 Danny

Om de removeHorizontalLane aan te roepen zijn alleen de coordinaten van de eerste tile en de lengte van de match nodig, dit maakt het een stuk makkelijker, de tileNumber, de coordinaten van de andere tiles, de breedte, de hoogte etc zijn allemaal niet nodig. Dit maakt het veel gemakkelijker voor een mede programmeur om deze functie aan te roepen en verlaagt de complexiteit.

removeHorizontalLane(x, y, i) {
    for(let m = x; m < x + i; m++) {
        for(let n = y; n > 0; n--) {
            if(this.#tiles[m][n-1].tileNumber != 0 && this.#tiles[m][n].tileNumber != 0) {
                this.#tiles[m][n] = new NormalTile(this.tileSize, m, n, this.#tiles[m][n-1].tileNumber);
            }
        }
            this.#tiles[m][0] = new NormalTile(this.tileSize, m, 0, Math.floor(random(1, 6)));
    }
}

Inheritance

1 Kai

De class “MovingTile” is een extensie op de code van de class “Tile” en erft daarom alle code van deze class.

In de constructor van de class, wordt daarom ook gelijk verwezen naar de constructor van de “super” class, in dit geval de Tile class.

Dit doen we om ervoor te zorgen dat we gemakkelijk soortgelijke classes kunnen maken, met kleine variaties en dezelfde basis.

class MovingTile extends Tile {
    constructor(size, x, y, tileNumber) {
        let image;
        if (tileNumber == 6) {
            image = gameManager.getImage("Poisonous Golden Dagger");
        } else if (tileNumber == 5) {
            image = gameManager.getImage("Golden Dagger");
        } else if (tileNumber == 4) {
            image = gameManager.getImage("Dagger");
        } else if (tileNumber == 3) {
            image = gameManager.getImage("Bow");
        } else if (tileNumber == 2) {
            image = gameManager.getImage("Shield");
        } else if (tileNumber == 1) {
            image = gameManager.getImage("Health");
        }
        super(image, size, x, y, tileNumber);
        this.image = image;
    }
}

2 Kai

Alle code van de class “Tile” kan nu worden gebruikt in de class “MovingTile” omdat het een extensie is.

De code hieronder is een extensie van de MovingTile class en hieruit halen ze ook hun image.

class NormalTile extends MovingTile {
    constructor(image, size, x, y, tileNumber) {
        super(image, size, x, y, tileNumber);
    }
}

``````

class SpecialTile extends MovingTile {
    constructor(image, size, x, y, tileNumber) {
        super(image, size, x, y, tileNumber);
    }
}

Ook al extenden ze beide van de class MovingTile, de SpecialTile class zorgt ervoor dat de damage die je doet verdubbelt.

Doe je dit niet, dan zou het er ongeveer zo uit zien

class NormalTile extends MovingTile {
    constructor(size, x, y, tileNumber) {
        let image;
        if (tileNumber == 6) {
            image = gameManager.getImage("Poisonous Golden Dagger");
        } else if (tileNumber == 5) {
            image = gameManager.getImage("Golden Dagger");
        } else if (tileNumber == 4) {
            image = gameManager.getImage("Dagger");
        } else if (tileNumber == 3) {
            image = gameManager.getImage("Bow");
        } else if (tileNumber == 2) {
            image = gameManager.getImage("Shield");
        } else if (tileNumber == 1) {
            image = gameManager.getImage("Health");
        }
        super(image, size, x, y, tileNumber);
        this.image = image;
    }
}

``````

class SpecialTile extends MovingTile {
    constructor(size, x, y, tileNumber) {
        let image;
        if (tileNumber == 6) {
            image = gameManager.getImage("Poisonous Golden Dagger");
        } else if (tileNumber == 5) {
            image = gameManager.getImage("Golden Dagger");
        } else if (tileNumber == 4) {
            image = gameManager.getImage("Dagger");
        } else if (tileNumber == 3) {
            image = gameManager.getImage("Bow");
        } else if (tileNumber == 2) {
            image = gameManager.getImage("Shield");
        } else if (tileNumber == 1) {
            image = gameManager.getImage("Health");
        }
        super(image, size, x, y, tileNumber);
        this.image = image;
    }
}

Dit zorgt ervoor dat er te veel code staat die je eigenlijk ook maar 1 keer hoeft te schrijven, zo hoef je dus niet steeds dingen te kopieren en te plakken.

1 Danny

Voor inheritance hebben we de volgende overerving gebruikt, de subclass erft de eigenschapen van de class “tile” over.

class NonMovingTile extends Tile {
    constructor(size, x, y, tileNumber) {
        let image;
        image = gameManager.getImage("blackTile"),
        super(image, size, x, y, tileNumber);
        this.image = image;
         this.tileNumber = 0;
    }
}

2 Danny

De bovenstaande voorbeelden extenden allemaal direct of indirect de Tile class. Door middel van inheritance hoeven we de tile class maar 1 keer goed uit te werken, hierna kan de class “gekopieerd” worden, dit is beter voor de programmeurs en tevens veiliger.

class Tile {
    #tileNumber;
    #x;
    #y;
    #size;
    #image;
    #visible;

... // veel getters en setters


    constructor(image, size, x, y, tileNumber) {
        this.#image = image;
        this.#size = size;
        this.#x = x;
        this.#y = y;
        this.#tileNumber = tileNumber
        this.#visible = true;
    }
}

K6 Genormaliseerde Relationele Database

Link naar de Database Documentatie

K7 UML diagrammen

Use Case Diagram: Kai

Alt text

In de Use Case Diagram kan je zien hoe de speler van het systeem gebruik maakt, hier laten we dus geen dingen zien die op de achtergrond gebeuren.

Bij het deel van de User genaamd Admin hebben we de 2 stukken code die nu werken, en een stuk waar we mee bezig zijn genaamd “Check Highest Unlocked Level”, hierbij gaan we natuurlijk ook nog meerdere andere checks neerzetten.

Sequence Diagram: Kai

Alt text

In de bovenstaande diagram tonen we aan hoe de speler met de gameManager en de database connect en de volgorde waarin dit gebeurt. Dit geeft een goed simpel voorbeeld van hoe het systeem op de achtergrond loopt.

Class diagram: Danny

Class Diagram

Activity Diagram: Danny

Activity Diagram

G4: Kwaliteitscriteria

Agile Methodiek

We hebben gebruik gemaakt van een SCRUM board, zo konden we gemakkelijk user stories uitkiezen en die uitwerken naar een werkend prototype, ook konden we nieuwe user stories maken naar aanleiding van feedback van spelers en testen. We committen elke dag wanneer er een feature af is met commit messages die beschrijven wat er is veranderd ten opzichte van de voorgaande versie. Ook zetten we comments waar nodig.

Code Conventions

Code Conventions

(Technische) Documentatie

Technische Documentatie

Communicatie

Voor communicatie hebben we voornamelijk Discord gebruikt, dit is een platform waarop je gemakkelijk mensen kan bereiken om bijvoorbeeld snel een vraag te stellen of belangrijke zaken te vermelden. Dit wordt vaak gezien als onprofessioneel, maar veel bedrijven gebruiken ook zelf Discord voor bijvoorbeeld player support, om nieuwe updates aan te kondigen of om feedback te verzamelen.


Last update: January 18, 2024