Skip to content

Documentatie

Tiles

Er zijn 4 types van normale tegels en 2 van speciale tegels en elke type heeft bijbehorende plaatje. De normale tegels zijn juwelen die je moet drie of meer in de rij zetten om ze laten te verwijderen. Terwijl de speciale tegels zijn in de vorm van hout of steen. Je kunt ze niet laten bewegen, maar ze hebben wel duurzaamheid die daalt als een tegel ernaast is vernietigd. Als die duurzaamheid 0 bereikt, wordt de speciale tegel ook vernietigd. (Er moest ook origineel een speciale tegel zijn die als je die aanraakt alle tegels op dezelfde rij of kolom verwijdert, maar die kond ik niet realiseren)

class Tile {
    //Dit zijn de coördinaten van een tile
    #x;
    #y;
    //Dit zijn de coördinaten van de tilegrid waarin de tegel geplaatst wordt
    #gridx;
    #gridy;
    //Dit is de grootte van de tegel
    #size;
    //De tegels hebben een bepaalde image afhankelijk van de type
    _image;
    #visible;
    _type;
    #shift;
    //Sommige tegels hebben durability (duurzaamheid) die daalt als de tegel ernaast is vernietigd
    #durability

    //Hier door gebruik van get en set worden de parameters van dit class gelezen en veranderd
    get x() {
        return this.#x;
    }

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

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

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

    get type() {
        return this._type;
    }

    set type(value) {
        this._type = value;
    }

    get image() {
        return this._image;
    }

    set image(value) {
        this._image = value;
    }

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

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

    get position() {
        let pos = createVector(this.#x, this.#y).mult(this.#size);
        return pos.add(this.#gridx, this.#gridy);
    }

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

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

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

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

    //Hier worden de tegels getekend
    draw(time) {
        let coord = this.getTileCoordinate(0, time * this.#shift);
        if (this.#visible && this._type > 0) {
            fill(128, 0, 0);
            stroke(80, 0, 0);
            strokeWeight(3);
            rect(coord.x, coord.y, this.#size, this.#size);
            image(this._image, coord.x, coord.y, this.#size, this.#size);
        }
    }
    //De functie die coördinaten berekent van waar de tegel meot getekend worden.
    getTileCoordinate(shiftx, shifty) {
        let tilex = this.position.x + shiftx * this.#size;
        let tiley = this.position.y + shifty * this.#size;
        return { x: tilex, y: tiley }
    }

    constructor(type, size, x, y, gridx, gridy) {
        this._type = type;
        this.#size = size;
        this.#x = x;
        this.#y = y;
        this.#gridx = gridx;
        this.#gridy = gridy;
        this.#shift = 0;
        this.#visible = true;
        if(type == 6){
            this.#durability = 3;
        }
        else if (type == 8){
            this.#durability = 5;
        }
        /*Normale tegels hebben oneindige duurzaamheid, waardor ze kunnien niet vernietigd worden
        tenzij ze in de cluster zijn.*/
        else{
            this.#durability = Infinity;
        }
    }
}

Grid

In de TileGrid class worden meeste functies uitgevoerd. Daar worden de tegels gemaakt en gezet, moves worden opgeslagen, en in geval er blijven geen meer moves over wordt de tile grid opnieuw gemaakt.

class TileGrid {

    //Hier zijn de coördinaten van de tileGrid
    #x;
    #y;
    //Hier is de parameter voor de grootte van de tegels
    #tileSize;
    #tiles;
    //Hier zijn de breedte en de hoogte van de canvas.
    #width;
    #height;
    //Hier is de nummer van de huidige level
    #level;
    //Hier zijn de arrays met types die gebruikt worden voor een specefieke level
    #typelist = [1, 2, 3, 4];
    #typelist2 = [1, 2, 3, 4, 6, 6];
    #typelist3 = [1, 2, 3, 4, 8, 8];
    #typelist4 = [1, 2, 3, 4, 6, 8];

    //Het staat de coördinaten voor de tile die nu is geselecteerd
    selectedTile = {
        selected: false,
        column: 0,
        row: 0
    }

    //In deze lijst worden later clusters gezet
    #clusters = [];
    //In deze lijst staan alle beschikbare moves
    #moves = [];

    //Hier staan de coördinaten voor de huidige twee tegels die geswapt worden
    #currentmove = {
        x1: 0,
        y1: 0,
        x2: 0,
        y2: 0
    }

    constructor(x, y, width, height, tileSize, level) {
        this.#x = x;
        this.#y = y;
        this.#tileSize = tileSize;
        this.#width = width;
        this.#height = height;
        this.#level = level;
        this.#generateTileGrid();
    }
    #generateTileGrid() {
        let done = false;
        //tiles is een 2D array, dat betekent dat het is een array met andere arrays daarbinnen. 
        this.#tiles = new Array();

        //Hier wordt de tile grid gegenereerd and de tegels worden in de 2D #tiles array geplaatst.
        while (done == false) {
            for (let x = 0; x < this.#width; x++) {
                for (let y = 0; y < this.#height; y++) {

                    if (!this.#tiles[x]) {
                        this.#tiles[x] = new Array();
                    }
                    if (this.#level == 1) {
                        this.#tiles[x][y] = new NormalTile(random(this.#typelist), this.#tileSize, x, y, this.#x, this.#y);
                    }
                    else if (this.#level == 2) {
                        this.#tiles[x][y] = new NormalTile(random(this.#typelist2), this.#tileSize, x, y, this.#x, this.#y);
                    }
                    else if (this.#level == 3) {
                        this.#tiles[x][y] = new NormalTile(random(this.#typelist3), this.#tileSize, x, y, this.#x, this.#y);
                    }
                    else if (this.#level == 4) {
                        this.#tiles[x][y] = new NormalTile(random(this.#typelist4), this.#tileSize, x, y, this.#x, this.#y);
                    }

                }
            }
            this.#resolveClusters();
            this.#findMoves();
            if (this.#moves.length > 0) {
                done = true;
            }
        }
    }
    //Hier wordt de tile grid opnieuw gemaakt in het geval als er blijven geen meer moves over.
    #changeTileGrid() {
        let done = false;
        while (done == false) {
            for (let x = 0; x < this.#width; x++) {
                for (let y = 0; y < this.#height; y++) {
                    if (this.#level == 1) {
                        this.#tiles[x][y].type = random(this.#typelist);
                    }
                    else if (this.#level == 2) {
                        this.#tiles[x][y].type = random(this.#typelist2);
                    }
                    else if (this.#level == 3) {
                        this.#tiles[x][y].type = random(this.#typelist3);
                    }
                    else if (this.#level == 4) {
                        this.#tiles[x][y].type = random(this.#typelist4);
                    }
                    this.#tiles[x][y].updateImage();
                }
            }
            this.#resolveClusters();
            this.#findMoves();
            if (this.#moves.length > 0) {
                done = true;
            }
        }
    }
    //Hier wordt gezocht naar beschikbare moves
    #findMoves() {
        this.#moves = [];
        for (let y = 0; y < this.#height; y++) {
            for (let x = 0; x < this.#width - 1; x++) {
                if (this.#tiles[x][y].type < 6 && this.#tiles[x + 1][y].type < 6) {
                    this.#swap(x, y, x + 1, y);
                    this.#findClusters();
                    this.#swap(x, y, x + 1, y);
                    if (this.#clusters.length > 0) {
                        this.#moves.push({ column1: x, row1: y, column2: x + 1, row2: y })
                    }
                }
            }
        }
        for (let x = 0; x < this.#width; x++) {
            for (let y = 0; y < this.#height - 1; y++) {
                if (this.#tiles[x][y].type < 6 && this.#tiles[x][y + 1].type < 6) {
                    this.#swap(x, y, x, y + 1);
                    this.#findClusters();
                    this.#swap(x, y, x, y + 1);
                    if (this.#clusters.length > 0) {
                        this.#moves.push({ column1: x, row1: y, column2: x, row2: y + 1 })
                    }
                }
            }
        }
        this.#clusters = [];
    }
    //Dit functie geeft de tegel terug afhankelijk van de positie
    getTileAtPosition(position) {
        const gridXPosition = Math.floor((position.x - this.#x) / this.#tileSize);
        const gridYPosition = Math.floor((position.y - this.#y) / this.#tileSize);

        return this.getTileAtGridIndex(gridXPosition, gridYPosition);
    }

    //Dit functie geeft de tegel terug afhankelijk van de grid index
    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];
    }
}

Clusters

Wanneer drie of meer tegels in een rij staan, een cluster wordt gevormd. Deze clusters worden eerst in de lijst gezet, daarna uit de lijst gehaald en uit de tile grid verwijderd.

class TileGrid {
    //In deze lijst worden later clusters gezet
    #clusters = [];
    //Dit functie zorgt voor vinden en verwijderen van clusters
    #resolveClusters() {
        this.#findClusters();
        while (this.#clusters.length > 0) {
            this.#removeClusters();
            this.#shiftTiles();
            this.#findClusters();
        }
    }
    //Hier wordt gekeken naar de aanwezigheid van de clusters
    #findClusters() {
        this.#clusters = [];
        //Zoekt horizontale clusters
        for (let y = 0; y < this.#height; y++) {
            let matchlength = 1;
            for (let x = 0; x < this.#width; x++) {
                let checkcluster = false;
                if (x == this.#width - 1) {
                    checkcluster = true;
                }
                else {
                    if (this.#tiles[x][y].type == this.#tiles[x + 1][y].type &&
                        this.#tiles[x][y].type != -1 && this.#tiles[x][y].type < 6) {
                        matchlength += 1;
                    }
                    else {
                        checkcluster = true;
                    }
                }
                if (checkcluster == true) {
                    if (matchlength >= 3) {
                        let cluster = {
                            column: x + 1 - matchlength,
                            row: y,
                            length: matchlength,
                            horizontal: true
                        }
                        this.#clusters.push(cluster);
                    }
                    matchlength = 1;
                }
            }
        }
        //Zoekt verticale clusters
        for (let x = 0; x < this.#width; x++) {
            let matchlength = 1;
            for (let y = 0; y < this.#height; y++) {
                let checkcluster = false;
                if (y == this.#height - 1) {
                    checkcluster = true;
                }
                else {
                    if (this.#tiles[x][y].type == this.#tiles[x][y + 1].type &&
                        this.#tiles[x][y].type != -1 && this.#tiles[x][y].type < 6) {
                        matchlength += 1;
                    }
                    else {
                        checkcluster = true;
                    }
                }
                if (checkcluster == true) {
                    if (matchlength >= 3) {
                        let cluster = {
                            column: x,
                            row: y + 1 - matchlength,
                            length: matchlength,
                            horizontal: false
                        }
                        this.#clusters.push(cluster);
                    }
                    matchlength = 1;
                }
            }
        }
    }
    //Dit functie zorgt voor de wisseling van de tiles
    #swap(x1, y1, x2, y2) {
        let typeswap = { 
            type: this.#tiles[x1][y1].type, 
            durability: this.#tiles[x1][y1].durability,
            image: this.#tiles[x1][y1].image 
            };
        this.#tiles[x1][y1].type = this.#tiles[x2][y2].type;
        this.#tiles[x1][y1].durability = this.#tiles[x2][y2].durability;
        this.#tiles[x1][y1].image = this.#tiles[x2][y2].image;
        this.#tiles[x2][y2].type = typeswap.type;
        this.#tiles[x2][y2].durability = typeswap.durability;
        this.#tiles[x2][y2].image = typeswap.image;
    }
    //Hier wordt gezocht naar beschikbare moves
    #findMoves() {
        this.#moves = [];
        for (let y = 0; y < this.#height; y++) {
            for (let x = 0; x < this.#width - 1; x++) {
                if (this.#tiles[x][y].type < 6 && this.#tiles[x + 1][y].type < 6) {
                    this.#swap(x, y, x + 1, y);
                    this.#findClusters();
                    this.#swap(x, y, x + 1, y);
                    if (this.#clusters.length > 0) {
                        this.#moves.push({ column1: x, row1: y, column2: x + 1, row2: y })
                    }
                }
            }
        }
        for (let x = 0; x < this.#width; x++) {
            for (let y = 0; y < this.#height - 1; y++) {
                if (this.#tiles[x][y].type < 6 && this.#tiles[x][y + 1].type < 6) {
                    this.#swap(x, y, x, y + 1);
                    this.#findClusters();
                    this.#swap(x, y, x, y + 1);
                    if (this.#clusters.length > 0) {
                        this.#moves.push({ column1: x, row1: y, column2: x, row2: y + 1 })
                    }
                }
            }
        }
        this.#clusters = [];
    }
    //Hier woordt door elke cluster over elke cluster
    #loopClusters() {
        for (let i = 0; i < this.#clusters.length; i++) {
            let cluster = this.#clusters[i];
            let coffset = 0;
            let roffset = 0;
            for (let j = 0; j < cluster.length; j++) {
                let tile = this.#tiles[cluster.column + coffset][cluster.row + roffset];
                tile.type = -1;
                //Hier worden de houten en steenrots tegels verwijderd
                for (let x = tile.x - 1; x <= tile.x + 1; x++) {
                    if (x < 0) {
                        continue;
                    }
                    else if (x >= this.#width) {
                        break;
                    }
                    for (let y = tile.y - 1; y <= tile.y + 1; y++) {
                        if (y < 0) {
                            continue;
                        }
                        else if (y >= this.#height) {
                            break;
                        }
                        let tile2 = this.getTileAtGridIndex(x, y);
                        if (tile2 != null && tile2.type >= 6) {
                            tile2.durability -= 1;
                            if (tile2.durability <= 0) {
                                tile2.type = -1;
                            }
                        }
                    }
                }
                if (cluster.horizontal) {
                    coffset++;
                }
                else {
                    roffset++;
                }
            }
        }
    }
    //Hiet worden de clusters weggehaald
    #removeClusters() {
        this.#loopClusters();
        for (let x = 0; x < this.#width; x++) {
            let shift = 0;
            for (let y = this.#height - 1; y >= 0; y--) {
                if (this.#tiles[x][y].type == -1) {
                    shift++;
                    this.#tiles[x][y].shift = 0;
                }
                else {
                    this.#tiles[x][y].shift = shift;
                }
            }
        }
    }
    //Hier worden de nieuwe tegels aangemaakt
    #shiftTiles() {
        for (let x = 0; x < this.#width; x++) {
            for (let y = this.#height - 1; y >= 0; y--) {
                if (this.#tiles[x][y].type == -1) {
                    if (this.score == 0) {
                        if(this.#level == 1){
                            this.#tiles[x][y].type = random(this.#typelist);
                        }
                        else if(this.#level == 2){
                            this.#tiles[x][y].type = random(this.#typelist2);
                        }
                    }
                    //Hier worden nieuwe tegels gemaakt
                    else {
                        this.#tiles[x][y].type = random(this.#typelist);
                    }
                    this.#tiles[x][y].updateImage();
                }
                else {
                    let shift = this.#tiles[x][y].shift;
                    if (shift > 0) {
                        this.#swap(x, y, x, y + shift);
                    }
                }
                this.#tiles[x][y].shift = 0;
            }
        }
    }
}

Swaps

Origineel, wanneer ik heb de code geschreven, de swaps werkten niet, en ik wist niet waarom, waardoor we liepen vast tijdens de eerste sprint. Later heb ik het probleem gevonden. Voor sommige reden ik dacht dat als ik de type van de tegels verander, de image zal samen met hun veranderen, maar het werkte zo niet, waardoor ik moest een functie maken die handmatig de image van de tegel zal veranderen. De andere probleem was dat de touch controls werkten niet, waardoor ik moest een aparte functie schrijven die afhankelijk van of de vinger of de muis is gebruikt op eigen manier de input van de speler leest.

class TileGrid {
    //Hier wordt gecontroleerd of de bepaalde twee tegels kunnen met elkaar omgewisseld worden
    canSwap(x1, y1, x2, y2) {
    if ((Math.abs(x1 - x2) == 1 && y1 == y2) ||
        (Math.abs(y1 - y2) == 1 && x1 == x2)) {
        return true;
    }
    return false;
}
    //Hier worden de coördinaten van de huidige move opgeslagen
    touchSwap(c1, r1, c2, r2) {
        this.#currentmove = { x1: c1, y1: r1, x2: c2, y2: r2 };
        this.selectedTile.selected = false;
        this.#animationstate = 2;
        this.#animationtime = 0;
        this.#gamestate = this.#gamestates.resolve;
    }
}
function touchStarted(event) {
    //Hier zijn functies die uitgevoerd worden aan het begin van de aanraking
    if (drag == false) {
        const tile = tileGrid.getTileAtPosition(getPosition(event));
        if (tile != null && tile.type < 6) {
            let swapped = false;
            if (tileGrid.selectedTile.selected == true) {
                if (tile.x == tileGrid.selectedTile.column &&
                    tile.y == tileGrid.selectedTile.row) {
                    tileGrid.selectedTile.selected = false;
                    drag = true;
                    return;
                }
                else if (tileGrid.canSwap(tile.x, tile.y, tileGrid.selectedTile.column, tileGrid.selectedTile.row)) {
                    tileGrid.touchSwap(tile.x, tile.y, tileGrid.selectedTile.column, tileGrid.selectedTile.row);
                    movesleft -= 1;
                    swapped = true;
                }

            }
            if (swapped == false) {
                tileGrid.selectedTile.column = tile.x;
                tileGrid.selectedTile.row = tile.y;
                tileGrid.selectedTile.selected = true;
            }
        }
        else {
            tileGrid.selectedTile.selected = false;
        }
        drag = true;
    }
}

//Deze functie wordt uitgevoerd als de speler zijn vinger beweegd
function touchMoved(event) {
    if (drag == true && tileGrid.selectedTile.selected == true) {
        const tile = tileGrid.getTileAtPosition(getPosition(event));
        if (tile != null && tile.type < 6) {
            if (tileGrid.canSwap(tile.x, tile.y, tileGrid.selectedTile.column, tileGrid.selectedTile.row)) {
                tileGrid.touchSwap(tile.x, tile.y, tileGrid.selectedTile.column, tileGrid.selectedTile.row);
                movesleft -= 1;
            }
        }
    }
}
//Deze functie wordt uitgevoerd als de speler zijn vinger loslaat
function touchEnded(event) {
    drag = false;
}
//Hier wordt de positie van de vinger of muis getraceerd
function getPosition(event) {
    let rect = canvas.getBoundingClientRect();
    //Afhankelijk van of de vinger of muis is gebruikt, wordtde positie op unieke manier getraceerd
    if (event.type == 'touchstart' || event.type == 'touchmove') {
        let evt = (typeof event.originalEvent === 'undefined') ? event : event.originalEvent;
        let touch = evt.touches[0] || evt.changedTouches[0]
        return createVector(touch.pageX - rect.left, touch.pageY - rect.top);
    }
    else if (event.type == 'mousedown' || event.type == 'mousemove') {
        return createVector(event.x - rect.left, event.y - rect.top);
    }
}

Animaties

En natuurlijk alles moet goed eruitzien, dus we hebben functies gemaakt om alles te animeren. Ook deze functies zorgen ervoor dat als de clusters verwijderd worden, de score en de aanvalskracht van de speler stijgt.

class TileGrid {
    //Hier staat de score van de speler.
    score = 0;
    //Hier staat de spelers aanvalskracht
    playerdamage = 0;

    #gamestates = {
        init: 0,
        ready: 1,
        resolve: 2
    }
    #gamestate = this.#gamestates.init;

    //Deze parameters zijn voor animaties bedoeld
    #animationstate = 0;
    #animationtime = 0;
    #animationtimetotal = 180;

    //Hier worden de animaties geregeld voor bewegen van de tegels
    #renderTiles(time) {
        let tile1 = this.#tiles[this.#currentmove.x1][this.#currentmove.y1];
        let tile2 = this.#tiles[this.#currentmove.x2][this.#currentmove.y2];
        if (this.#gamestate == this.#gamestates.resolve && (this.#animationstate == 2 || this.#animationstate == 3)) {
            let shiftx = tile2.x - tile1.x;
            let shifty = tile2.y - tile1.y;
            let coord1shift = tile1.getTileCoordinate(time * shiftx, time * shifty);
            let image1 = tile1.image;
            let coord2shift = tile2.getTileCoordinate(time * -shiftx, time * -shifty);
            let image2 = tile2.image;
            tile1.visible = false;
            tile2.visible = false;
            fill(128, 0, 0);
            stroke(25);
            strokeWeight(3);
            if (this.#animationstate == 2) {
                rect(coord1shift.x, coord1shift.y, this.#tileSize, this.#tileSize);
                image(image1, coord1shift.x, coord1shift.y, this.#tileSize, this.#tileSize);
                rect(coord2shift.x, coord2shift.y, this.#tileSize, this.#tileSize);
                image(image2, coord2shift.x, coord2shift.y, this.#tileSize, this.#tileSize);
            }
            //Als de wisseling van tegels geen clusters vormt, worden de tegels teruggewisseld
            else {
                rect(coord2shift.x, coord2shift.y, this.#tileSize, this.#tileSize);
                image(image2, coord2shift.x, coord2shift.y, this.#tileSize, this.#tileSize);
                rect(coord1shift.x, coord1shift.y, this.#tileSize, this.#tileSize);
                image(image1, coord1shift.x, coord1shift.y, this.#tileSize, this.#tileSize);
            }
        }
        else {
            tile1.visible = true;
            tile2.visible = true;
        }
    }
    //Hier worden de resultaten van de player input gehandeld
    update(deltaTime) {
        if (this.#gamestate == this.#gamestates.ready && this.#moves.length <= 0) {
            this.#changeTileGrid();
        }
        else if (this.#gamestate == this.#gamestates.resolve) {
            this.#animationtime += deltaTime;
            if (this.#animationstate == 0) {
                if (this.#animationtime > this.#animationtimetotal) {
                    this.#findClusters();
                    if (this.#clusters.length > 0) {
                        for (let i = 0; i < this.#clusters.length; i++) {
                            this.score += 100 * (this.#clusters[i].length - 2);
                            this.playerdamage += 10 * (this.#clusters[i].length - 2);
                        }
                        this.#removeClusters()
                        this.#animationstate = 1;
                    }
                    else {
                        this.#gamestate = this.#gamestates.ready;
                    }
                    this.#animationtime = 0;
                }
            }
            //Animationstate voor bewegen van de tegels na verwijdering van clusters
            else if (this.#animationstate == 1) {
                if (this.#animationtime > this.#animationtimetotal) {
                    this.#shiftTiles();
                    this.#animationstate = 0;
                    this.#animationtime = 0;
                    this.#findClusters();
                    if (this.#clusters.length <= 0) {
                        this.#gamestate = this.#gamestates.ready
                    }
                }
            }
            //Animationstate voor wisseling van tegels
            else if (this.#animationstate == 2) {
                if (this.#animationtime > this.#animationtimetotal) {
                    this.#swap(this.#currentmove.x1, this.#currentmove.y1, this.#currentmove.x2, this.#currentmove.y2);
                    this.#findClusters();
                    if (this.#clusters.length > 0) {
                        this.#animationstate = 0;
                        this.#animationtime = 0;
                        this.#gamestate = this.#gamestates.resolve;
                    }
                    else {
                        this.#animationstate = 3;
                        this.#animationtime = 0;
                    }
                    this.#findMoves();
                    this.#findClusters();
                }
            }
            //Animationstate voor terugwisseling van tegels
            else if (this.#animationstate == 3) {
                if (this.#animationtime > this.#animationtimetotal) {
                    this.#swap(this.#currentmove.x1, this.#currentmove.y1, this.#currentmove.x2, this.#currentmove.y2);
                    this.#gamestate = this.#gamestates.ready;
                }
            }
            this.#findMoves();
            this.#findClusters();
        }
    }

    //Hier wordt de tile grid getekend
    draw() {
        fill(0);
        textSize(75);
        noStroke();
        text(this.score, this.#x, this.#y - this.#tileSize / 3);
        fill(165, 42, 42);
        stroke(25);
        strokeWeight(5);
        rect(this.#x, this.#y, this.#width * this.#tileSize, this.#height * this.#tileSize);
        for (let x = 0; x < this.#width; x++) {
            for (let y = 0; y < this.#height; y++) {
                this.#tiles[x][y].draw(this.#animationtime / this.#animationtimetotal);
            }
        }
        this.update(deltaTime);
        this.#renderTiles(this.#animationtime / this.#animationtimetotal);
    }
    //Det functie controleert of de tile grid stilstaat
    get act() {
        if (this.#gamestate == this.#gamestates.ready) {
            return true;
        }
        else {
            return false;
        }
    }
}

Bronnen

Rembound. (2020, 17 februari). How to make a match-3 game with HTML5 canvas. Rembound. https://rembound.com/articles/how-to-make-a-match3-game-with-html5-canvas
Rembound. (z.d.). Match-3-Game-HTML5/match3-example.js at Master · Rembound/Match-3-Game-HTML5. GitHub. https://github.com/rembound/Match-3-Game-HTML5/blob/master/match3-example.js
Touch events. (z.d.). https://www.w3schools.com/jsref/obj_touchevent.asp
Mouse events. (z.d.). https://www.w3schools.com/jsref/obj_mouseevent.asp
Touch - Web APIs | MDN. (2023, 20 november). MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/API/Touch
Determine touch position on tablets with JavaScript. (z.d.). Stack Overflow. https://stackoverflow.com/questions/41993176/determine-touch-position-on-tablets-with-javascript/61732450#61732450

Personages

Sinds onze game is deels een rpg waar je moet monsters verslaan, we hebben die ook toegevoegd. We hebben ook sub-classes gemaakt voor de held en voor de vijanden. Elk personage heeft eigen hp (aantal health points), aanvalskracht (of, in andere woorden, damage) en sprites.

class Character {
    //De coördianten van waar de personage moet getekend worden
    #x
    #y
    #role
    //De huidige en maximale hp van de personage
    #hp
    #maxhp
    _damage
    _spritesheet
    #size
    #color

    //Hier door gebruik van get en set worden de parameters van dit class gelezen en veranderd
    get x() {
        return this.#x;
    }

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

    get hp() {
        return this.#hp
    }

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

    get damage() {
        return this._damage;
    }

    set damage(value) {
        this._damage = value;
    }

    get position() {
        return createVector(this.#x, this.#y).mult(this.#size);
    }

    constructor(x, y, role, hp, size, color) {
        this.#x = x;
        this.#y = y;
        this.#role = role;
        this.#hp = hp;
        this.#maxhp = hp;
        this.#size = size;
        this.#color = color
        this.#animationstate = 1;
    }
}
class Hero extends Character {

    //Hier wordt de held aangemaakt
    constructor(x, y, size) {
        super(x, y, "pc", 500, size, color(0, 0, 255));
        this._damage = 0;
        this._spritesheet = {
            idle: gameManager.getImage("knight_idle"),
            attack: gameManager.getImage("knight_attack"),
            hurt: gameManager.getImage("knight_hurt"),
            dead: gameManager.getImage("knight_dead")
        }
    }
}
class Enemy extends Character {
    #moves
    #mindmg
    #maxdmg

    get moves() {
        return this.#moves
    }
    //Hier wordt de willekeurige aanvalskracht gemaakt tussen minimale en maximale waarde
    #getRndInteger(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    updateDamage() {
        this._damage = this.#getRndInteger(this.#mindmg, this.#maxdmg);
    }

    constructor(type, x, y) {
        if (type == "skeleton") {
            super(x, y, "npc", 400, 300, color(255, 0, 0));
            this.#moves = 3;
            this.#mindmg = 50;
            this.#maxdmg = 75;
            this._spritesheet = {
                idle: gameManager.getImage("skeleton_idle"),
                attack: gameManager.getImage("skeleton_attack"),
                hurt: gameManager.getImage("skeleton_hurt"),
                dead: gameManager.getImage("skeleton_dead")
            }
        }
        else if (type == "slime") {
            super(x, y, "npc", 250, 150, color(255, 0, 0));
            this.#moves = 3;
            this.#mindmg = 25;
            this.#maxdmg = 50;
            this._spritesheet = {
                idle: gameManager.getImage("slime_idle"),
                attack: gameManager.getImage("slime_attack"),
                hurt: gameManager.getImage("slime_hurt"),
                dead: gameManager.getImage("slime_dead")
            }
        }
    }
}

Animaties

En natuurlijk, de personages moeten bewegen, dus we hebben spritesheets uit de internet gedownload en die geanimeerd.

class Character {
    //De coördianten van waar de personage moet getekend worden
    #x
    #y
    #role
    //De huidige en maximale hp van de personage
    #hp
    #maxhp
    _damage
    _spritesheet
    #size
    #color

    //Deze parameters zijn voor animaties bedoeld
    #animationstate = 0;
    #animationtime = 0;
    #animationtimetotal = 900;

    //De twee functies bewgen zorgen voor het tekenen van de personages
    draw(sprite) {
        let frame;
        let time = Math.floor(this.#animationtime / (this.#animationtimetotal / Math.round(sprite.width / 128)));
        const distance = 128;
        let spritex;
        const spritesize = this.#size * 1.5;
        if (this.#role == "pc") {
            frame = distance * time;
            spritex = this.#x;
        }
        else if (this.#role == "npc") {
            frame = sprite.width - distance * (1 + time);
            spritex = this.#x - (spritesize - this.#size) / 2;
        }
        //Hier wordt de healthbar getekend
        fill(255, 0, 0);
        stroke(0);
        strokeWeight(5);
        rect(this.#x, this.#y, this.#size, 30, 10);
        if (this.#hp > 0) {
            fill(0, 255, 0);
            stroke(0);
            strokeWeight(3);
            rect(this.#x, this.#y, this.#size * (this.#hp / this.#maxhp), 30, 10);
        }
        //Bij aanval wordt de aanvalskracht weergegeven
        if (this.#animationstate == 2) {
            fill(this.#color);
            textSize(100);
            textFont(gameManager.getFont("medieval"));
            text(this._damage, this.#x + this.#size / 3, this.#y - this.#size);
        }
        image(sprite, spritex, this.#y - spritesize, spritesize, spritesize, frame, 0, sprite.height, sprite.height);
        //Hier wordt de animatie opnieuw gespeelt
        if ((frame >= sprite.width && this.#role == "pc") || (frame < 0 && this.#role == "npc")) {
            if (this.#animationstate > 0 && this.#animationstate < 4) {
                this.#animationstate = 1;
                frame = 0;
                this.#animationtime = 0;
            }
            else if (this.#animationstate == 4) {
                this.#animationstate = 0;
            }
        }
    }

    //Hier wordt bepaald welke animatie moet getekend worden
    update(deltaTime) {
        if (this.#animationstate > 0) {
            this.#animationtime += deltaTime;
            if (this.#animationstate == 1) {
                this.draw(this._spritesheet.idle);
            }
            else if (this.#animationstate == 2) {
                this.draw(this._spritesheet.attack);
            }
            else if (this.#animationstate == 3) {
                this.draw(this._spritesheet.hurt);
            }
            else if (this.#animationstate == 4) {
                this.draw(this._spritesheet.dead);
            }
        }
    }

    constructor(x, y, role, hp, size, color) {
        this.#x = x;
        this.#y = y;
        this.#role = role;
        this.#hp = hp;
        this.#maxhp = hp;
        this.#size = size;
        this.#color = color
        this.#animationstate = 1;
    }
}

Bronnen

Ram, P. (2019, 29 augustus). How to build a simple sprite animation in JavaScript. Medium. https://medium.com/dailyjs/how-to-build-a-simple-sprite-animation-in-javascript-b764644244aa

Aanval

De damage van de held stijgt wanneer de tegels zijn verwijderd. Hoe meer tegels zijn verwijderd per move, hoe hoger de damage van de held. Terwijl de vijand heeft vaste minimale en maximale damage en de damage die de vijand uitvoert tijdens zijn beurt is een willekeurige getal tussen minimum en maximum.

class Character {
    //De functies bewegen worden uitgevoerd afhakelijk van of de personage valt aan, geraakt wordt, of doodgaat.
    attack(enemy) {
        if (this.#hp > 0) {
            this.#animationstate = 2;
            this.#animationtime = 0;
            enemy.hp -= this._damage;
            enemy.hurt();
        }
    }

    hurt() {
        this.#animationstate = 3;
        this.#animationtime = 0;
    }

    death() {
        this.#animationstate = 4;
    }
}
//Dit is de functie voor gevecht
function fight() {
    timer();
    hero.update(deltaTime);
    for (let enemy of enemies) {
        enemy.update(deltaTime);
        if (enemy.hp <= 0){
            if(enemy.act != 0){
                enemy.death();
            }
            else{
                enemies = enemies.filter(checkHP);
            }
        }
    }
    if (tileGrid.act == true) {
        if (tileGrid.playerdamage > 0) {
            hero.damage = tileGrid.playerdamage;
            hero.attack(enemies[0]);
            tileGrid.playerdamage = 0;
        }
        else if (hero.act != 2 && movesleft <= 0) {
            for (let enemy of enemies) {
                enemy.updateDamage();
                enemy.attack(hero);
                tileGrid.score -= 100;
                movesleft = enemies[0].moves;
            }
        }
    }
}
//Hier wordt gecontroleerd op hoeveel hp blijft er over
function checkHP(character){
    return character.hp > 0;
}

HTML Elementen

Net zoals voige blok, ik heb een div gemaakt waarin ik kan de canvas en andere HTML-elementen plaatsen (zodat HTML-elementen worden ten opzichte van de canvas gepositioneerd).

<body>
  <main>
    <!--In dit div wordt later canvas en andere html-elementen geplaatst die met javascript worden gecreërd-->
    <div id="canvas-wrap"></div>
  </main>
</body>
html {
    height: 100%;
}

body {
    margin: 0;
    padding: 0;
    background-image: url('assets/images/windowbg.jpg');
    background-repeat: no-repeat;
    background-size: 100% 100%;
    height: 100%;
    justify-content: center;
    align-items: center;
}

main {
    min-height: 100%;
}

canvas {
    padding: 0;
    display: block;
    position: absolute;
    margin: auto;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
}

/*Hier zijn styles voor een div waarin canvas en html-elementen worden geplaatst die met javascript gemaakt worden.*/
#canvas-wrap {
    padding: 0;
    margin: auto;
    width: 800px;
    height: 1400px;
    /*Hier wordt een rand voor de canvas gemaakt.*/
    border: 10px;
    border-radius: 5px;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}
button {
    margin: auto;
    position: relative;
    background: rgba(255, 255, 255, 0.2);
    color: white;
    text-align: center;
    border-color: white;
    border-style: solid;
    border-width: thin;
    cursor: pointer;
    font-family: 'MedievalSharp', cursive;
    right: 0;
    top: 700px;
    bottom: 0;
}

Bronnen

Reference | P5.js. (z.d.-m). https://p5js.org/reference/#/p5/createElement
Placing a div within a canvas. (z.d.). Stack Overflow. https://stackoverflow.com/questions/5763911/placing-a-div-within-a-canvas

Winnen en verliezen

Als de held alle hp verliest, gaat de held dood en heeft de speler verloren. Als de vijand alle hp verliest, gaat die dood en wordt die uit de lijst met de vijanden verwijderd. Als er blijven geen meer vijanden over, de speler wint. Daarna ziet de speler een scherm met zijn score en de scoreboard. Ook zijn er twee knoppen om de level opnieuw te spelen of om terug naar de menu gaan.

//Dit is de functie voor gevecht
function fight() {
    if (hero.hp <= 0) {
        if (hero.act != 0) {
            hero.death();
        }
        else {
            gameManager.saveResults(minutes + ':' + seconds, moves, "lose", tileGrid.score);
            defeat = true;
        }
    }
    else if (enemies.length == 0) {
        gameManager.saveResults(minutes + ':' + seconds, moves, "win", tileGrid.score);
        victory = true;
    }
}
//Hier wordt gecontroleerd op hoeveel hp blijft er over
function checkHP(character) {
    return character.hp > 0;
}
//Dit functie laat de resultaat zien van de level
function gameResult() {
    gameManager.UpdateScore(tileGrid.score, tileGrid.levelnr, minutes + ':' + seconds);
    textSize(72);
    textFont(font);
    noStroke();
    if (victory == true) {
        fill(0, 0, 255);
        text("Victory", width / 3, 200);
    }
    if (defeat == true) {
        fill(255, 0, 0);
        text("Game Over", width / 4, 200);
    }
    //Hier wordt de score weergegeven
    textSize(48);
    text("Your score: " + tileGrid.score, width / 4, 350);
    //Hier worden de knoppen aangemaakt om de level te verlaten
    let retryButton = document.createElement("button");
    retryButton.setAttribute("type", "button");
    retryButton.innerHTML = "Try Again";
    retryButton.setAttribute("onclick", "exitLevel(true)");
    retryButton.setAttribute("class", "result");
    let returnButton = document.createElement("button");
    returnButton.setAttribute("type", "button");
    returnButton.innerHTML = "Exit to the menu";
    returnButton.setAttribute("onclick", "exitLevel(false)");
    returnButton.setAttribute("class", "result");
    returnButton.style.left = "150px";
    if (resultappend == true) {
        LeaderBoard();
        canvaswrap.appendChild(retryButton);
        canvaswrap.appendChild(returnButton);
        resultappend = false;
    }
}
//Dit is de functie om de level te verlaten en terug naar de menu gaan
function exitLevel(retry) {
    removeHTMLElements("result");
    removeHTMLElements("scoreboard");
    victory = false;
    defeat = false;
    done = false;
    seconds = 0;
    minutes = 0;
    moves = 0;
    if (retry == true) {
        generateLevel(tileGrid.levelnr);
    }
    else {
        levelappend = true;
    }
}
//Dit functie is om html-elemeten te verwijderen.
function removeHTMLElements(classname) {
    let elements = document.getElementsByClassName(classname);
    const amount = elements.length;
    for (let i = 0; i < amount; i++) {
        elements[0].remove();
    }
}
.result{  
    padding: 5px;
    display: inline-block;
    border-radius: 5px;
    font-size: 50px;
    left: 100px;
    top: 650px;
}

Startscherm

Aan het begin van het spel, ziet de speler een startscherm. Als de speler nieuw is, wordt er gevraagd om zijn naam in te voeren. Dan wordt een unieke code voor de speler gemaakt en opgeslagen op de computer (meer daarover in de “Database” deel);

function StartScreen() {
    //Hier wordt de titel getekend
    textSize(96);
    fill(255, 215, 0);
    textFont(font);
    text('Jewel', width / 3, 200);
    text('Quest', width / 3, 300);
    //Hier wordt de functie uitgevoerd om een nieuwe speler te maken
    if (gameManager.newplayer == true) {
        EnterName();
    }
    //Hier wordt een knop aangemaakt om het spel te beginnen
    else {
        let playButton = document.createElement("button");
        playButton.setAttribute("type", "button");
        playButton.innerHTML = "Play";
        playButton.setAttribute("onclick", "enterTheGame()");
        playButton.setAttribute("class", "startbutton");
        if (loginappend == true) {
            playButton.style.fontSize = "96px";
            canvaswrap.appendChild(playButton);
            loginappend = false;
        }
    }
}
function EnterName() {
    textSize(56);
    textFont(font);
    fill(255);
    text('Please enter your name', width / 10, 650);
    let nameInput = document.createElement("input");
    nameInput.setAttribute("type", "text");
    nameInput.setAttribute("id", "userName");
    nameInput.setAttribute("placeholder", "Username");
    nameInput.setAttribute("oninput", "turnButtonOn(submitButton)");
    nameInput.setAttribute("autocomplete", "off");
    let submitButton = document.createElement("button");
    submitButton.setAttribute("type", "button");
    submitButton.setAttribute("disabled", "true")
    submitButton.innerHTML = "Confirm";
    submitButton.setAttribute("onclick", "enterTheGame()");
    submitButton.setAttribute("class", "startbutton");
    submitButton.setAttribute("id", "submitButton");
    if (loginappend == true) {
        canvaswrap.appendChild(nameInput);
        canvaswrap.appendChild(submitButton);
        loginappend = false;
    }
}
//Dit is een functie om een knop aan te zetten als een naam (die moet ten minste 3 letters lang zijn) was ingevuld.
function turnButtonOn(button) {
    failure = false;
    let userName = document.getElementById("userName").value;
    if (userName.length >= 3) {
        button.disabled = false;
    }
    else if (userName.length < 3) {
        button.disabled = true;
    }
}
//Hier wordt een nieuwe speler aangemaakt (als dat nodig is) en html-elementen verwijderen
function enterTheGame() {
    if (gameManager.newplayer == true) {
        gameManager.savePlayer();
    }
    let inputbar = document.getElementById("userName");
    if (inputbar != null) {
        inputbar.remove();
    }
    removeHTMLElements("startbutton");
    start = false;
}
input {
    padding: 5px;
    display: inline-block;
    margin: auto;
    margin-bottom: 100px;
    position: relative;
    display: block;
    width: auto;
    border: 2px solid;
    border-radius: 10px;
    background: rgba(0, 0, 0, 0);
    top: 750px;
    bottom: 0;
    left: 0;
    right: 0;
    color: white;
    justify-content: center;
    font-size: 50px;
}
.startbutton{
    padding: 5px;
    display: block;
    border-radius: 5px;
    font-size: 64px;
}

Meerdere levels

Nadat de speler heeft de game gestart, ziet die een scherm met vijf knoppen voor elk level. Bij het aanklikken van de knop wordt de bijbehorende level aangemaakt. Nadat je een level voltooit, wordt de volgende level ontgrendeld.

function LevelSelect() {
    textSize(96);
    fill(215, 0, 0);
    textFont(font);
    text('Select level', 150, 250);
    let levelProgress = localStorage.getItem("levelProgress");
    for (let i = 1; i < 6; i++) {
        let levelbutton = document.createElement("button");
        levelbutton.setAttribute("type", "button");
        levelbutton.setAttribute("class", "levelbutton");
        levelbutton.innerHTML = i;
        if (i > levelProgress) {
            levelbutton.setAttribute("disabled", "true");
        }
        levelbutton.setAttribute("onclick", "generateLevel(this.innerHTML)");
        if (i >= 4) {
            levelbutton.style.top = "750px";
            levelbutton.style.left = "200px";
        }
        if (levelappend == true) {
            canvaswrap.appendChild(levelbutton);
            if (i == 5) {
                levelappend = false;
            }
        }
    }
}
//Dit is een functie om een nieuwe level aan te maken
function generateLevel(levelnr) {
    removeHTMLElements("levelbutton");
    if (levelnr > 0) {
        if (!done) {
            tileGrid = new TileGrid(gridx, gridy, tileWidth, tileHeight, tileSize, levelnr);
            if (levelnr == 1) {
                hero = new Hero(50, chary, 300);
                let enemy = new Enemy("skeleton", width - 400, chary);
                enemies.push(enemy);
                movesleft = enemy.moves;
            }
            else if (levelnr == 2) {
                hero = new Hero(50, chary, 250);
                for (let i = 0; i < 2; i++) {
                    let enemy = new Enemy("slime", width - 400 + 200 * i, chary);
                    enemies.push(enemy);
                }
                movesleft = enemies[0].moves;
            }
            else if (levelnr == 3) {
                hero = new Hero(50, chary, 300);
                let enemy = new Enemy("werewolf", width - 400, chary);
                enemies.push(enemy);
                movesleft = enemy.moves;
            }
            else if (levelnr == 4) {
                hero = new Hero(50, chary, 250);
                for (let i = 0; i < 2; i++) {
                    let enemy = new Enemy("slime", width - 400 + 200 * i, chary);
                    enemies.push(enemy);
                }
                movesleft = enemies[0].moves;
            }
            else if (levelnr == 5) {
                hero = new Hero(50, chary, 300);
                let enemy = new Enemy("wizard", width - 400, chary);
                enemies.push(enemy);
                movesleft = enemy.moves;
            }
            //Hier wordt een nieuwe speelsessie in de database aangemaakt
            gameManager.createSession(levelnr);
            done = true;
        }
    }
}
/*Hier staan styles die aan de levelknoppen toegepast moeten worden.*/
.levelbutton {
    display: inline-block;
    margin-right: 100px;
    font-size: 120px;
    border-radius: 20px;
    left: 75px;
    height: 150px;
    width: 150px;
}

Timer

Net als vorige blok, ik heb een timer toegevoegd om te tijd erbij te halen.

function timer() {
    seconds = constrain(seconds % 60, 0, 60);
    // Hiet wordt een text geschreven die laat de tijd zien.
    fill(255, 215, 0);
    stroke(128, 0, 0);
    strokeWeight(5);
    rect(gridx + (tileWidth * tileSize) * 2 / 3, gridy - 100, tileWidth * tileSize / 3, 75, 20);
    textSize(64);
    fill(75, 0, 0);
    textFont(font);
    stroke(0);
    strokeWeight(1);
    // Hier worden frames in seconden omgezet
    if (frameCount % fr == 0) {
        seconds += 1;
    }
    //Bij 60 seconden stijgt het aantal minuten.
    if (seconds == 60) {
        minutes += 1;
    }
    let time;
    //Afhankelijk van aantal minuten en seconden zal de tijd anders eruitzien
    if (seconds <= 9) {
        if (minutes <= 9) {
            time = '0' + minutes + ':0' + seconds;
        }
        else if (minutes > 9) {
            time = minutes + ':0' + seconds;
        }
    }
    else if (seconds > 9) {
        if (minutes <= 9) {
            time = '0' + minutes + ':' + seconds;
        }
        else if (minutes > 9) {
            time = minutes + ':' + seconds;
        }
    }
    text(time, gridx + (tileWidth * tileSize) * 2 / 3, gridy - tileSize / 3);
}

Bronnen

Reference | P5.js. (z.d.-h). https://p5js.org/reference/#/p5/frameCount
Remainder (%) - JavaScript | MDN. (2023, 25 augustus). https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder

Actergrond en fonts

Fonts en images worden in de AssetManager geladen. Voor de achtergrond hebben we een grot gekozen voor de bovenste deel van de canvas, een stenen achtergrond voor de onderste deel van de canvas, en een middeleeuwse rol voor de rest van het scherm. En voor de font heb ik een middeleeuwse font uit de internet gedownload.

class AssetManager {
    static #instance;
    #images;
    #fonts;
    constructor() {
        this.#images = new Map();
        this.#fonts = new Map();
        this.#loadImages();
        this.#loadFonts();
        window.assetManager = this;
    }

    #loadImages() {
        this.#images.set("diamond", loadImage("assets/images/diamond.svg"));
        this.#images.set("emerald", loadImage("assets/images/emerald.png"));
        this.#images.set("ruby", loadImage("assets/images/ruby.png"));
        this.#images.set("sapphire", loadImage("assets/images/sapphire.png"));
        this.#images.set("obsidian", loadImage("assets/images/obsidian.png"));
        this.#images.set("wood", loadImage("assets/images/wood.png"));
        this.#images.set("stone", loadImage("assets/images/stone.png"));
        this.#images.set("lair", loadImage("assets/images/lair.jpg"));
        this.#images.set("background", loadImage("assets/images/rockybg.jpg"));
        this.#images.set("knight_idle", loadImage("assets/sprites/Knight/Idle.png"));
        this.#images.set("knight_attack", loadImage("assets/sprites/Knight/Attack.png"));
        this.#images.set("knight_hurt", loadImage("assets/sprites/Knight/Hurt.png"));
        this.#images.set("knight_dead", loadImage("assets/sprites/Knight/Dead.png"));
        this.#images.set("skeleton_idle", loadImage("assets/sprites/Skeleton/Idle.png"));
        this.#images.set("skeleton_attack", loadImage("assets/sprites/Skeleton/Attack.png"));
        this.#images.set("skeleton_hurt", loadImage("assets/sprites/Skeleton/Hurt.png"));
        this.#images.set("skeleton_dead", loadImage("assets/sprites/Skeleton/Dead.png"));
        this.#images.set("slime_idle", loadImage("assets/sprites/Slime/Idle.png"));
        this.#images.set("slime_attack", loadImage("assets/sprites/Slime/Attack.png"));
        this.#images.set("slime_hurt", loadImage("assets/sprites/Slime/Hurt.png"));
        this.#images.set("slime_dead", loadImage("assets/sprites/Slime/Dead.png"));
        this.#images.set("wizard_idle", loadImage("assets/sprites/Wizard/Idle.png"));
        this.#images.set("wizard_attack", loadImage("assets/sprites/Wizard/Attack.png"));
        this.#images.set("wizard_hurt", loadImage("assets/sprites/Wizard/Hurt.png"));
        this.#images.set("wizard_dead", loadImage("assets/sprites/Wizard/Dead.png"));
        this.#images.set("werewolf_idle", loadImage("assets/sprites/Werewolf/Idle.png"));
        this.#images.set("werewolf_attack", loadImage("assets/sprites/Werewolf/Attack.png"));
        this.#images.set("werewolf_hurt", loadImage("assets/sprites/Werewolf/Hurt.png"));
        this.#images.set("werewolf_dead", loadImage("assets/sprites/Werewolf/Dead.png"));
    }

    #loadFonts(){
        this.#fonts.set("medieval", loadFont("assets/fonts/Medieval.ttf"));
    }

    getImage(assetname) {
        try {
            return this.#images.get(assetname);
        } catch (exc) {
            throw new Error("file does not exist!");
        }
    }

    getFont(assetname){
        try {
            return this.#fonts.get(assetname);
        } catch (exc) {
            throw new Error("file does not exist!");
        }
    }
}
class GameManager {
    #analyticsTrackerManager;
    #assetManager;

    constructor() {
        this.#analyticsTrackerManager = new AnalyticsTrackerManager();
        this.#assetManager = new AssetManager();
        window.gameManager = this;
    }

    getImage(assetname) {
        return this.#assetManager.getImage(assetname);
    }
    getFont(assetname){
        return this.#assetManager.getFont(assetname);
    }

}

function preload() {
    new GameManager();
    //Hier worden de images en fonts geladen
    lair = gameManager.getImage("lair");
    bgimage = gameManager.getImage("background");
    font = gameManager.getFont("medieval");
    canvaswrap = document.getElementById("canvas-wrap");
}
function draw() {
    image(bgimage, 0, width * 2 / 3, width, width * 3 / 2);
    //Hier wordt de achergrond getekend en functies uitgevoerd
    image(lair, 0, 0, width, width * 2 / 3);
    noStroke();
    if (start == true) {
        StartScreen();
    }
    else if (done == false && start == false) {
        LevelSelect();
    }
    else if (victory == false && defeat == false && done == true) {
        fight()
        tileGrid.draw();
    }
    else if (victory == true || defeat == true) {
        gameResult();
    }
}

Bronnen

MedievalSharp - Google Fonts. (z.d.). Google Fonts. https://fonts.google.com/specimen/MedievalSharp
Reference | P5.js. (z.d.-k). https://p5js.org/reference/#/p5/textFont
Reference | P5.js. (z.d.-i). https://p5js.org/reference/#/p5/image

Database

Nadat ik heb tabellen bij de database gemaakt, heb ik voor elk tabel eigen php-bestand gemaakt om gegevens eerst in de database op te slaan, en daarna die uit de database te halen (bij sommigen alleen om op te slaan).

Huidige_Database

User.php

Hier wordt de speler in de database gemaakt en uit de gatabase gehaald

// Handle request type logic
switch ($requestMethod) {
    case 'PUT':
        // nothing yet  
        break;
    case 'POST':
        // README: https://lornajane.net/posts/2008/accessing-incoming-put-data-from-php
        $params = file_get_contents('php://input'); // Access the request PUT data
        $paramsArray = json_decode($params, true);
        createPlayer($paramsArray);
        break;
    case 'GET':
        findPlayer();
        break;
    default:
        handleError('Invalid HTTP request method');
        break;

}



function findPlayer()
{

    if (isset($_GET['code'])) {
        $playerUniqueCode = $_GET["code"];
        $dbReturnID = 0;
        $dbAanmaakDatum = 0;
        $dbUsername = NULL;

        // Search for player in the database based on GET param playerName 
        try {
            $dbConnect = new DatabaseConnection();

            $playerUniqueCode = mysqli_real_escape_string($dbConnect->getConnection(), $playerUniqueCode);

            $statement = "
            SELECT 
                id, aanmaakDatumTijd, Username
            FROM
                Blok2_Speler
            WHERE
                uniqueCode = '" . $playerUniqueCode . "';
            ";


            // Execute query-statement on the database
            $result = $dbConnect->executeQuery($statement);

            if (!$result) {
                $errorMsg = $dbConnect->getConnection()->error;
                handleError('' . $errorMsg);
            } else {
                if ($result->num_rows > 0) {
                    $row = $result->fetch_assoc();
                    $dbReturnID = $row['id'];
                    $dbAanmaakDatum = $row['aanmaakDatumTijd'];
                    $dbUsername = $row['Username'];
                    // Return JSON structured data with requested/needed information about the existing player
                    echo '{"responseType":"ok", "aanmaakDatumTijd":"' . $dbAanmaakDatum . '", "id":' . $dbReturnID . ', 
                        "Username":"' . $dbUsername . '"}';
                } else {
                    // No records found, return error notifying the user about this.
                    handleNotFound('user with uniqueCode ' . $playerUniqueCode);
                }
            }

            // Free the result set
            $result->free();

        } catch (Exception $e) {
            $dbReturnID = -1;
            $errorMsg = 'Failed to query the database. Err: ' . $e->getMessage();
            handleError('' . $errorMsg);
        }

    } else {
        handleError('No unqiue player code received via GET, or use POST with param createPlayer : true to create a new player.');
    }

}

function createPlayer($paramsPostArray)
{
    $errorMsg = NULL;
    $bCreatePlayer = NULL;
    $bUsername = NULL;
    if (isset($paramsPostArray["createPlayer"], $paramsPostArray["userName"])) {

        $uniqueDBCode = -1;
        $dbReturnId = -1;
        $bCreatePlayer = $paramsPostArray["createPlayer"];
        $bUsername = $paramsPostArray["userName"];

        if ($bCreatePlayer == true) {

            // Add a new player to the database and return player data (row id, uniqueCode, aanmaakDatum)
            try {
                $dbConnect = new DatabaseConnection();

                // Generate a unique token of 23 characters with a 'player_' prefix (30 characters)
                $uniqueDBCode = uniqid('player_', true);

                $statement = "
                INSERT INTO Blok2_Speler
                    (uniqueCode, aanmaakDatumTijd, Username)
                VALUES
                    ('" . $uniqueDBCode . "', now(), '" . $bUsername . "');
                ";

                // Execute query-statement on the Database
                $bSucces = $dbConnect->executeQuery($statement);

                if (!$bSucces) {
                    $errorMsg = $dbConnect->getConnection()->error;
                    handleError('Insert failed: ' . $errorMsg);
                } else {
                    $dbReturnId = $dbConnect->getConnection()->insert_id;
                    // Return JSON structured data with requested/needed information about the new player
                    echo '{"responseType":"ok", "uniqueCode": "' . $uniqueDBCode . '"}';
                }

            } catch (Exception $e) {
                $dbReturnId = -1;
                $errorMsg = 'Failed to query the database. Err: ' . $e->getMessage();
                handleError('' . $errorMsg);
            }
        }
    } else {
        handleError('No createPlayer POST param received');
    }
}

Session.php

Dit is om een nieuwe sessie aan te makken.

// Handle request type logic
switch ($requestMethod) {
    case 'PUT':
        // nothing yet  
        break;
    case 'POST':
        // README: https://lornajane.net/posts/2008/accessing-incoming-put-data-from-php
        $params = file_get_contents('php://input'); // Access the request PUT data
        $paramsArray = json_decode($params, true);
        createSession($paramsArray);
        break;
    case 'GET':
        handleError('Invalid HTTP request method');
        break;
    default:
        handleError('Invalid HTTP request method');
        break;

}


function createSession($paramsPostArray)
{
    $errorMsg = NULL;
    $SpelerUniqueCode = NULL;
    $bLevelID = NULL;
    if (isset($paramsPostArray["PlayerCode"], $paramsPostArray["Levelnumber"])) {

        $dbReturnId = -1;
        $SpelerUniqueCode = $paramsPostArray["PlayerCode"];
        $bLevelID = $paramsPostArray["Levelnumber"];

        // Add a new player to the database and return player data (row id, uniqueCode, aanmaakDatum)
        try {
            $dbConnect = new DatabaseConnection();

            $statement = "
            INSERT INTO Blok2_Speelsessie
                (Blok2_Speler_uniqueCode, Level_id, startDatumTijd)
            VALUES
                ('" . $SpelerUniqueCode . "', '" . $bLevelID . "', now());
            ";

            // Execute query-statement on the Database
            $bSucces = $dbConnect->executeQuery($statement);

            if (!$bSucces) {
                $errorMsg = $dbConnect->getConnection()->error;
                handleError('Insert failed: ' . $errorMsg);
            } else {
                $dbReturnId = $dbConnect->getConnection()->insert_id;
                // Return JSON structured data with requested/needed information about the new player
                echo '{"responseType":"ok", "id": "' . $dbReturnId . '"}';
            }

        } catch (Exception $e) {
            $dbReturnId = -1;
            $errorMsg = 'Failed to query the database. Err: ' . $e->getMessage();
            handleError('' . $errorMsg);
        }
    } else {
        handleError('No createPlayer POST param received');
    }
}

Measurepoint.php

Dit is om de meetpunten op te slagen

// Handle request type logic
switch ($requestMethod) {
    case 'PUT':
        // nothing yet  
        break;
    case 'POST':
        // README: https://lornajane.net/posts/2008/accessing-incoming-put-data-from-php
        $params = file_get_contents('php://input'); // Access the request PUT data
        $paramsArray = json_decode($params, true);
        addMeasurePoint($paramsArray);
        break;
    case 'GET':
        handleError('Invalid HTTP request method');
        break;
    default:
        handleError('Invalid HTTP request method');
        break;

}

function addMeasurePoint($paramsPostArray)
{
    $errorMsg = NULL;
    $bTime = NULL;
    $bMoves = NULL;
    $bResult = NULL;
    $bScore = NULL;
    $bSpeelsessieId = NULL;
    if (isset($paramsPostArray["gameTime"], $paramsPostArray["moves"], $paramsPostArray["result"], $paramsPostArray["score"],
    $paramsPostArray["SpeelsessieId"])) {

        $bTime = $paramsPostArray["gameTime"];
        $bMoves = $paramsPostArray["moves"];
        $bResult = $paramsPostArray["result"];
        $bScore = $paramsPostArray["score"];
        $bSpeelsessieId = $paramsPostArray["SpeelsessieId"];

        // Add a new player to the database and return player data (row id, uniqueCode, aanmaakDatum)
        try {
            $dbConnect = new DatabaseConnection();


            $statement = "
            INSERT INTO Blok2_Meetpunt
                (inGameTime, moves, result, score, Speelsessie_id)
            VALUES
                ('" . $bTime . "','" . $bMoves . "','" . $bResult . "','" . $bScore . "', '" . $bSpeelsessieId . "');
            ";

            // Execute query-statement on the Database
            $bSucces = $dbConnect->executeQuery($statement);

            if (!$bSucces) {
                $errorMsg = $dbConnect->getConnection()->error;
                handleError('Insert failed: ' . $errorMsg);
            } else {
                // Return JSON structured data with requested/needed information about the new player
                echo '{"responseType":"ok", "Speelsessie_id": "' . $bSpeelsessieId . '"}';
            }

        } catch (Exception $e) {
            $errorMsg = 'Failed to query the database. Err: ' . $e->getMessage();
            handleError('' . $errorMsg);
        }
    } else {
        handleError('No createPlayer POST param received');
    }
}

Tile.php

Hier worden de type en de coördinaten opgeslagen van de tiles die de speler liet bewegen.

// Handle request type logic
switch ($requestMethod) {
    case 'PUT':
        // nothing yet  
        break;
    case 'POST':
        // README: https://lornajane.net/posts/2008/accessing-incoming-put-data-from-php
        $params = file_get_contents('php://input'); // Access the request PUT data
        $paramsArray = json_decode($params, true);
        addTile($paramsArray);
        break;
    case 'GET':
        handleError('Invalid HTTP request method');
        break;
    default:
        handleError('Invalid HTTP request method');
        break;

}


function addTile($paramsPostArray)
{
    $errorMsg = NULL;
    $bcoordinates = NULL;
    $bSpeelsessieId = NULL;
    $btype = NULL;
    if (isset($paramsPostArray["coordinates"], $paramsPostArray["SessieID"], $paramsPostArray["type"])) {

        $dbReturnId = -1;
        $bcoordinates = $paramsPostArray["coordinates"];
        $bSpeelsessieId = $paramsPostArray["SessieID"];
        $btype = $paramsPostArray["type"];

        // Add a new player to the database and return player data (row id, uniqueCode, aanmaakDatum)
        try {
            $dbConnect = new DatabaseConnection();

            $statement = "
            INSERT INTO Blok2_Tile
                (coördinaten, Speelsessie_id, type)
            VALUES
                ('" . $bcoordinates . "', '" . $bSpeelsessieId . "', '" . $btype . "');
            ";

            // Execute query-statement on the Database
            $bSucces = $dbConnect->executeQuery($statement);

            if (!$bSucces) {
                $errorMsg = $dbConnect->getConnection()->error;
                handleError('Insert failed: ' . $errorMsg);
            } else {
                $dbReturnId = $dbConnect->getConnection()->insert_id;
                // Return JSON structured data with requested/needed information about the new player
                echo '{"responseType":"ok", "coordinates": "' . $bcoordinates . '"}';
            }

        } catch (Exception $e) {
            $dbReturnId = -1;
            $errorMsg = 'Failed to query the database. Err: ' . $e->getMessage();
            handleError('' . $errorMsg);
        }
    } else {
        handleError('No createPlayer POST param received');
    }
}
De Scoreboard heeft twee php bestanden: één om scores op te slaan en om de hele scoreboard te laden, en de andere om scores te updaten en een specefieke score uit de database te halen.

Scoreboard.php

Bij loadScoreboard() worden meerdere rijen teruggegeven.

// Handle request type logic
switch ($requestMethod) {
    case 'PUT':
        // nothing yet  
        break;
    case 'POST':
        // README: https://lornajane.net/posts/2008/accessing-incoming-put-data-from-php
        $params = file_get_contents('php://input'); // Access the request PUT data
        $paramsArray = json_decode($params, true);
        postScore($paramsArray);
        break;
    case 'GET':
        loadScoreboard();
        break;
    default:
        handleError('Invalid HTTP request method');
        break;

}



function loadScoreboard()
{

    if (isset($_GET['Levelnumber'])) {
        $LevelID = $_GET["Levelnumber"];
        $dbReturnName = NULL;
        $dbHighscore = 0;

        // Search for player in the database based on GET param playerName 
        try {
            $dbConnect = new DatabaseConnection();

            $LevelID = mysqli_real_escape_string($dbConnect->getConnection(), $LevelID);

            $statement = "
            SELECT 
                Speler_Username, Highscore
            FROM
                Blok2_Scoreboard
            WHERE
                Level_id = '" . $LevelID . "'
            ORDER BY
                Highscore DESC, Time ASC;
            ";


            // Execute query-statement on the database
            $result = $dbConnect->executeQuery($statement);

            if (!$result) {
                $errorMsg = $dbConnect->getConnection()->error;
                handleError('' . $errorMsg);
            } else {
                if ($result->num_rows > 0) {
                    $rows = $result->fetch_all(MYSQLI_ASSOC);
                    $array = array();
                    foreach ($rows as $row) {
                        $dbReturnName = $row['Speler_Username'];
                        $dbHighscore = $row['Highscore'];
                        // Return JSON structured data with requested/needed information about the existing player
                        array_push($array, json_decode('{"responseType":"ok", "UserName":"' . $dbReturnName . '", "Highscore":' . $dbHighscore . '}'));
                    }
                    echo(json_encode($array));

                } else {
                    // No records found, return error notifying the user about this.
                    handleNotFound('scores with level_id ' . $LevelID);
                }
            }

            // Free the result set
            $result->free();

        } catch (Exception $e) {
            $dbReturnID = -1;
            $errorMsg = 'Failed to query the database. Err: ' . $e->getMessage();
            handleError('' . $errorMsg);
        }

    } else {
        handleError('No unqiue player code received via GET, or use POST with param createPlayer : true to create a new player.');
    }

}

function postScore($paramsPostArray)
{
    $errorMsg = NULL;
    $bHighScore = NULL;
    $bLevelID = NULL;
    $bSpelerUsername = NULL;
    $bTime = NULL;
    if (
        isset($paramsPostArray["highscore"], $paramsPostArray["Levelnumber"],
        $paramsPostArray["SpelerUserName"], $paramsPostArray["gameTime"])
    ) {

        $dbReturnId = -1;
        $bHighScore = $paramsPostArray["highscore"];
        $bLevelID = $paramsPostArray["Levelnumber"];
        $bSpelerUsername = $paramsPostArray["SpelerUserName"];
        $bTime = $paramsPostArray["gameTime"];

        // Add a new player to the database and return player data (row id, uniqueCode, aanmaakDatum)
        try {
            $dbConnect = new DatabaseConnection();

            $statement = "
            INSERT INTO Blok2_Scoreboard
                (Highscore, Level_id, Speler_Username, Time)
            VALUES
                ('" . $bHighScore . "', '" . $bLevelID . "', '" . $bSpelerUsername . "', '" . $bTime . "');
            ";

            // Execute query-statement on the Database
            $bSucces = $dbConnect->executeQuery($statement);

            if (!$bSucces) {
                $errorMsg = $dbConnect->getConnection()->error;
                handleError('Insert failed: ' . $errorMsg);
            } else {
                $dbReturnId = $dbConnect->getConnection()->insert_id;
                // Return JSON structured data with requested/needed information about the new player
                echo '{"responseType":"ok", "id": "' . $dbReturnId . '",
                    "Speler_Username": "' . $bSpelerUsername . '", "Highscore": "' . $bHighScore . '"}';
            }

        } catch (Exception $e) {
            $dbReturnId = -1;
            $errorMsg = 'Failed to query the database. Err: ' . $e->getMessage();
            handleError('' . $errorMsg);
        }
    } else {
        handleError('No createPlayer POST param received');
    }
}

Scoreboard2.php

// Handle request type logic
switch ($requestMethod) {
    case 'PUT':
        // nothing yet  
        break;
    case 'POST':
        // README: https://lornajane.net/posts/2008/accessing-incoming-put-data-from-php
        $params = file_get_contents('php://input'); // Access the request PUT data
        $paramsArray = json_decode($params, true);
        updateScore($paramsArray);
        break;
    case 'GET':
        getScore();
        break;
    default:
        handleError('Invalid HTTP request method');
        break;

}



function getScore()
{

    if (isset($_GET['Levelnumber'], $_GET['PlayerName'])) {
        $LevelID = $_GET["Levelnumber"];
        $dbPlayerName = $_GET["PlayerName"];
        $dbHighscore = 0;

        // Search for player in the database based on GET param playerName 
        try {
            $dbConnect = new DatabaseConnection();

            $LevelID = mysqli_real_escape_string($dbConnect->getConnection(), $LevelID);
            $dbPlayerName = mysqli_real_escape_string($dbConnect->getConnection(), $dbPlayerName);

            $statement = "
            SELECT 
                Highscore
            FROM
                Blok2_Scoreboard
            WHERE
                Level_id = '" . $LevelID . "'
            AND
                Speler_Username = '" . $dbPlayerName . "'
            ";


            // Execute query-statement on the database
            $result = $dbConnect->executeQuery($statement);

            if (!$result) {
                $errorMsg = $dbConnect->getConnection()->error;
                handleError('' . $errorMsg);
            } else {
                if ($result->num_rows > 0) {
                    $row = $result->fetch_assoc();
                    $dbHighscore = $row['Highscore'];
                    // Return JSON structured data with requested/needed information about the existing player
                    echo '{"responseType":"ok", "Highscore":' . $dbHighscore . '}';
                } else {
                    // No records found, return error notifying the user about this.
                    handleNotFound('score with Username ' . $dbPlayerName);
                }
            }

            // Free the result set
            $result->free();

        } catch (Exception $e) {
            $errorMsg = 'Failed to query the database. Err: ' . $e->getMessage();
            handleError('' . $errorMsg);
        }

    } else {
        handleError('No unqiue player code received via GET, or use POST with param createPlayer : true to create a new player.');
    }

}

function updateScore($paramsPostArray)
{
    $errorMsg = NULL;
    $bHighScore = NULL;
    $bLevelID = NULL;
    $bSpelerUsername = NULL;
    $bTime = NULL;
    if (isset($paramsPostArray["highscore"], $paramsPostArray["Levelnumber"], 
    $paramsPostArray["SpelerUserName"], $paramsPostArray["gameTime"])) {

        $bHighScore = $paramsPostArray["highscore"];
        $bLevelID = $paramsPostArray["Levelnumber"];
        $bSpelerUsername = $paramsPostArray["SpelerUserName"];
        $bTime = $paramsPostArray["gameTime"];

        // Add a new player to the database and return player data (row id, uniqueCode, aanmaakDatum)
        try {
            $dbConnect = new DatabaseConnection();

            $statement = "
            UPDATE Blok2_Scoreboard
            SET Highscore = '" . $bHighScore . "', Time = '" . $bTime . "'
            WHERE
                Speler_Username = '" . $bSpelerUsername . "' 
            AND 
                Level_id = '" . $bLevelID . "';
            ";

            // Execute query-statement on the Database
            $bSucces = $dbConnect->executeQuery($statement);

            if (!$bSucces) {
                $errorMsg = $dbConnect->getConnection()->error;
                handleError('Insert failed: ' . $errorMsg);
            } else {
                // Return JSON structured data with requested/needed information about the new player
                echo '{"responseType":"ok", "Highscore": "' . $bHighScore . '"}';
            }

        } catch (Exception $e) {
            $dbReturnId = -1;
            $errorMsg = 'Failed to query the database. Err: ' . $e->getMessage();
            handleError('' . $errorMsg);
        }
    } else {
        handleError('No createPlayer POST param received');
    }
}

Javascript

class AnalyticsTrackerManager {

    playerID;
    newplayer = false;
    #newscore = true;
    #playerName
    #sessionID
    #oldscore
    constructor() {
        //get a unique id and store it in the localstorage.
        //retrieve from localStorage if available, otherwise create a new one.
        this.#initialize();
    }

    async #initialize() {
        const playerGUID = localStorage.getItem("playerGUID");
        const levelProgress = localStorage.getItem("levelProgress");
        if (playerGUID === null) {
            this.newplayer = true;
        } else {
            this.#retrievePlayerData(playerGUID);
        }
        if (levelProgress === null){
            localStorage.setItem("levelProgress", 1);
        }
    }

    //Dit functie haalt de data van de speler op uit de database
    #retrievePlayerData(uniqueCode) {
        let url = `https://oege.ie.hva.nl/~akimovm/blok2/analytics/user.php?code=${uniqueCode}`;
        httpGet(url, 'json', (responce)  => {
            this.playerID = responce.id;
            this.#playerName = responce.Username
            this.newplayer = false;
        })
    }

    //Dit functie slaat de data van de speler op in de database
    storePlayerData() {
        // create a p5 httppost connection
        let username = document.getElementById("userName").value;
        const postData = { 
            "createPlayer": true, 
            "userName": username 
        };
        const url = `https://oege.ie.hva.nl/~akimovm/blok2/analytics/user.php`;
        httpPost(url, 'json', postData, (result)  => {
            const playerUniqueCode = result.uniqueCode;
            localStorage.setItem("playerGUID", playerUniqueCode);
            localStorage.setItem("levelProgress", 1);
            this.#retrievePlayerData(playerUniqueCode);
        }, function (error) { throw new Error(error); });
    }

    //Dit functie maakt de nieuwe speelsessie aan
    createSession(levelid) {
        //track a custom event. Make sure to include the playerGUID.
        const playerGUID = localStorage.getItem("playerGUID");
        const postData = {
            "PlayerCode": playerGUID,
            "Levelnumber": levelid
        }
        const url = `https://oege.ie.hva.nl/~akimovm/blok2/analytics/session.php`;
        httpPost(url, 'json', postData, (result) => {
            this.#sessionID = result.id;
        }, function (error) { throw new Error(error); })
    }

    //Dit functie slaat de meetpunten van de huidige speelsessie op
    saveResults(time, moves, result, score){
        const postData = {
            "gameTime": time,
            "moves": moves,
            "result": result,
            "score": score,
            "SpeelsessieId": this.#sessionID
        }
        const url = `https://oege.ie.hva.nl/~akimovm/blok2/analytics/measurepoint.php`;
        httpPost(url, 'json', postData, (result) => {
            console.log(result);
        }, function (error) { throw new Error(error); });
    }

    //Dit functie slaat de coördinaten en de type van de tiles op
    saveTile(x, y, type){
        const postData = {
            "coordinates": "(" + x + "," + y + ")",
            "SessieID": this.#sessionID,
            "type": type
        }
        const url = `https://oege.ie.hva.nl/~akimovm/blok2/analytics/tile.php`;
        httpPost(url, 'json', postData, (result) => {
            console.log(result);
        }, function (error) { throw new Error(error); });
    }

    //Dit functie slaat de highscore op
    #saveHighscore(score, levelnr, time){
        const postData = {
            "highscore": score,
            "Levelnumber": levelnr,
            "SpelerUserName": this.#playerName,
            "gameTime": time
        }
        const url = `https://oege.ie.hva.nl/~akimovm/blok2/analytics/scoreboard.php`;
        httpPost(url, 'json', postData, (result) => {
            console.log(result);
            localStorage.setItem("levelProgress", levelnr + 1);

        }, function (error) { throw new Error(error); });
    }

    //Dit functie haalt de highscore uit de database
    #loadScore(levelnr){
        let url = `https://oege.ie.hva.nl/~akimovm/blok2/analytics/scoreboard2.php?Levelnumber=${levelnr}&PlayerName=${this.#playerName}`;
        httpGet(url, 'json', (responce)  => {
            this.#oldscore = responce.Highscore;
        })
    }

    //Dit functie vergelijkt de oude en de nieuwe score en update de highscore als het nodig is
    UpdateScore(score, levelnr, time){
        this.#loadScore(levelnr);
        if(this.#oldscore === null && this.#newscore == true){
            this.#saveHighscore(score, levelnr, time);
            this.#newscore = false;
        }
        else if(this.#oldscore != null && score > this.#oldscore){
            const postData = {
                "highscore": score,
                "Levelnumber": levelnr,
                "SpelerUserName": this.#playerName,
                "gameTime": time
            }
            const url = `https://oege.ie.hva.nl/~akimovm/blok2/analytics/scoreboard2.php`;
            httpPost(url, 'json', postData, (result) => {
                console.log(result);
            }, function (error) { throw new Error(error); });
        }
    }
}
class GameManager {
    #analyticsTrackerManager;
    #assetManager;

    constructor() {
        this.#analyticsTrackerManager = new AnalyticsTrackerManager();
        this.#assetManager = new AssetManager();
        window.gameManager = this;
    }

    //Hier worden de functies van de analyticsTrackerManager uitgevoerd
    savePlayer(){
        this.#analyticsTrackerManager.storePlayerData();
    }
    createSession(levelnr){
        this.#analyticsTrackerManager.createSession(levelnr);
    }
    saveResults(time, moves, result, score){
        this.#analyticsTrackerManager.saveResults(time, moves, result, score);
    }
    saveTile(x, y, type){
        this.#analyticsTrackerManager.saveTile(x, y, type);
    }
    UpdateScore(score, levelnr, time){
        this.#analyticsTrackerManager.UpdateScore(score, levelnr, time);
    }

    get newplayer(){
        return this.#analyticsTrackerManager.newplayer;
    }
    get playerID(){
        return this.#analyticsTrackerManager.playerID;
    }
    get data(){
        return this.#analyticsTrackerManager.data;
    }
}

Bronnen

Window localStorage property. (z.d.-b). https://www.w3schools.com/jsref/prop_win_localstorage.asp
PHP Mysqli Fetch_all() function. (z.d.). https://www.w3schools.com/php/func_mysqli_fetch_all.asp
PHP: Mysqli_result::Fetch_all - Manual. (z.d.). https://www.php.net/manual/en/mysqli-result.fetch-all.php

LeaderBoard

Alle highscores van de spelers worden in de Scoreboard opgeslagen en laten zien. Elk level heeft een eigen scoreboard.

function LeaderBoard() {
    let scoreboard = document.createElement("ol");
    scoreboard.setAttribute("type", "1");
    scoreboard.setAttribute("class", "scoreboard");
    //Hier wordt het aan html file gekoppeld.
    if (resultappend == true) {
        canvaswrap.appendChild(scoreboard);
    }
    let data;
    //Hier wordt een lijst voor scores gemaakt
    let url = `https://oege.ie.hva.nl/~akimovm/blok2/analytics/scoreboard.php?Levelnumber=${tileGrid.levelnr}`;
    httpGet(url, 'json', function (responce) {
        //Hier worden de scores en de bijbehorende namen naar de lijst toegevoegd.
        for (let i = 0; i < responce.length; i++) {
            data = responce[i];
            let boardEntry = document.createElement("li");
            boardEntry.innerHTML = data.UserName + ": " + data.Highscore;
            scoreboard.appendChild(boardEntry);
        }
    })
}
/*Dit is voor de lijst met scores*/
ol {
    width: 600px;
    height: 600px;
    border: 5px solid white;
    border-radius: 20px;
    overflow: auto;
    margin: auto;
    position: relative;
    top: 600px;
    left: 0;
}

/*Dit is voor wat binnen de lijst staat*/
li {
    color: white;
    margin-top: 10px;
    margin-bottom: 10px;
    margin-left: 25px;
    font-family: 'MedievalSharp', cursive;
    font-size: 48px;
}

Last update: January 23, 2024