Expert Review (Technische documentatie)¶
K5: OO-programming¶
Bij deze project heb ik gebruik gemaakt van 3 technieken van object-georiënteerd programmeren: abstraction, inheritance, and encapsulation. Hier beneden staat hoe ik heb die gebruikt.
Abstraction¶
Bij abstraction heb ik aan een specefieke regel gehouden: de parameters zijn public alleen als ze buiten de class gebruikt zijn, zoals hier uitgebeeld: parameters zoals tiles, typelist, clusters en moves zijn private, en dus buiten de class Tilegrid zijn niet gezien of gebruikt, terwijl selectedTile, score en gameover zijn wel gebruikt (of zullen gebruikt worden) buiten de class, waardoor ze zijn public.
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, 6, 8];
#typelist4 = [1, 2, 3, 4, 8, 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
}
//Hier staat de score van de speler.
score = 0;
//Hier staat de spelers aanvalskracht
playerdamage = 0;
//Hier wordt bepaald welek functie is de grid aan het uitvoeren.
#gamestates = {
init: 0,
ready: 1,
resolve: 2
}
#gamestate = this.#gamestates.init;
//Deze parameters zijn voor animaties bedoeld
#animationstate = 0;
#animationtime = 0;
#animationtimetotal = 180;
//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;
}
}
//Deze functie wordt uitgevoerd als de speler zijn vinger beweegd
function touchMoved(event) {
let rect = canvas.getBoundingClientRect();
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;
}
}
}
}
Inheritance¶
De class Character heeft twee subclasses: Hero en Enemy. Hero heeft dezelfde parameters en functies als zijn parent class, terwijl enemy voegd zijn eigen parameters toe, zoals moves, en eigen funcites, zoals updateDamage();
class Character {
#x
#y
#role
#hp
#maxhp
_damage
_spritesheet
#size
#color
//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;
}
//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;
}
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);
}
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);
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;
}
}
}
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;
}
}
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")
}
}
}
}
Encapsulation¶
Er zijn drie soorten modifiers die toegang tot parameters voor andere files beperken: public, private, en protected. Public parameters kunnen worden gezien en bewerkt binnen en buiten de class. Private parameters (met #-prefix) kunnen gezien en bewerkt worden alleen binnen dezelfde class. Protected (met _-prefix) werkt net zoals private, alleen de subclasses hebben ook toegang tot deze parameters.
class Tile {
#x;
#y;
#gridx;
#gridy;
#size;
_image;
#visible;
_type;
#shift;
}
class NormalTile extends Tile {
updateImage(){
if (this._type == 1){
this._image = gameManager.getImage("diamond");
}
if (this._type == 2){
this._image = gameManager.getImage("emerald");
}
if (this._type == 3){
this._image = gameManager.getImage("ruby");
}
if (this._type == 4){
this._image = gameManager.getImage("sapphire");
}
}
}
class Tile {
get x() {
return this.#x;
}
set x(value) {
this.#x = value;
}
get y() {
return this.#y;
}
set y(value) {
this.#y = value;
}
get image(){
return this._image;
}
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;
}
}
Bronnen¶
Janssen, T. (2023, 18 maart). OOP concept for beginners: What is inheritance? Stackify. https://stackify.com/oop-concept-inheritance/
Janssen, T. (2023b, mei 1). OOP concept for Beginners: What is encapsulation. Stackify. https://stackify.com/oop-concept-for-beginners-what-is-encapsulation/
Janssen, T. (2023b, mei 1). OOP concept for Beginners: What is abstraction? Stackify. https://stackify.com/oop-concept-abstraction/#
Kantor, I. (z.d.). Private and protected properties and methods. https://javascript.info/private-protected-properties-methods
EisenbergEffect. (2023, 3 oktober). Public, private, and protected class visibility patterns in JavaScript. Medium. https://eisenbergeffect.medium.com/public-private-and-protected-class-visibility-patterns-in-javascript-a23a29229430
Parwinder. (2020, 5 september). Classes in JS: public, private and protected. DEV Community. https://dev.to/bhagatparwinder/classes-in-js-public-private-and-protected-1lok
JavaScript accessors. (z.d.). https://www.w3schools.com/js/js_object_accessors.asp
K6: Relationele database¶
Ik ben nog niet ver gegaan met de database, maar ik heb al progress gemaakt. Bijvoorbeeld ik kan nu database gebruiken voor de login systeem. Dit heb ik gedaan door user.php te modificeren. Nu slaat die bestand username en wachtwoord van de speler, en zoekt de speler door gebruik van username en wachtwoord. Ook heb ik de database gemodificeerd door nieuwe tabellen en columns toe te voegen.
// 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;
// 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
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'];
// Return JSON structured data with requested/needed information about the existing player
echo '{"responseType":"ok", "aanmaakDatumTijd":"' . $dbAanmaakDatum . '", "id":' . $dbReturnID . '}';
} 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');
}
}
class AnalyticsTrackerManager {
playerID;
newplayer = false;
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");
if (playerGUID === null) {
this.newplayer = true;
} else {
this.#retrievePlayerData(playerGUID);
}
}
#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.newplayer = false;
})
}
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);
this.#retrievePlayerData(playerUniqueCode);
}, function (error) { throw new Error(error); });
}
}
class GameManager {
#analyticsTrackerManager;
#assetManager;
constructor() {
this.#analyticsTrackerManager = new AnalyticsTrackerManager();
this.#assetManager = new AssetManager();
window.gameManager = this;
}
savePlayer(){
this.#analyticsTrackerManager.storePlayerData();
}
get newplayer(){
return this.#analyticsTrackerManager.newplayer;
}
get playerID(){
return this.#analyticsTrackerManager.playerID;
}
}
let start = true;
function draw() {
if (start == true) {
StartScreen();
}
}
function StartScreen() {
textSize(96);
fill(255, 215, 0);
textFont(font);
text('Jewel', width / 3, 200);
text('Quest', width / 3, 300);
if (gameManager.newplayer == true) {
EnterName();
}
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;
}
}
function enterTheGame() {
if (gameManager.newplayer == true) {
gameManager.savePlayer();
}
let inputbar = document.getElementById("userName");
if (inputbar != null) {
inputbar.remove();
}
removeHTMLElements("startbutton");
start = false;
}
Bronnen¶
Reference | P5.js. (z.d.). https://p5js.org/reference/#/p5/httpPost
Reference | P5.js. (z.d.-b). https://p5js.org/reference/#/p5/httpGet
Here, S. N. (z.d.). Stappenplan opzetten PHP en het gebruik van een database - HBO-ICT Match 3. https://propedeuse-hbo-ict.dev.hihva.nl/onderwijs/opdrachtomschrijvingen-jaar1-blok2/gd-opdracht/lesplan_php/
PHP isset() function. (z.d.). https://www.w3schools.com/php/func_var_isset.asp
PHP Mysqli Real_Escape_String() function. (z.d.). https://www.w3schools.com/php/func_mysqli_real_escape_string.asp
Window localStorage property. (z.d.). https://www.w3schools.com/jsref/prop_win_localstorage.asp
K7: Documentatie met UML¶
Om UML Diagrammen te maken heb ik gebruik gemaakt van (een VSC extentie voor) UMLet, een tool om UML diagrammen te maken. Nadat ik heb die gemaakt, heb ik die geëxporteerd als png’s en hier geplakt. Hier beneden zijn Use Case en Class diagrammen voor de huidige code en in de toekomst zal het aangepast worden.

Bronnen¶
Propedeuse, T. (z.d.). UML - Knowledgebase. https://knowledgebase.hbo-ict-hva.nl/1_beroepstaken/software/ontwerpen/uml/0_uml/
Propedeuse, T. (z.d.-b). Use case diagram - Knowledgebase. https://knowledgebase.hbo-ict-hva.nl/1_beroepstaken/software/ontwerpen/uml/uml_use_case_diagram/
Propedeuse, T. (z.d.-b). UML Class Diagram - Knowledgebase. https://knowledgebase.hbo-ict-hva.nl/1_beroepstaken/software/ontwerpen/uml/uml_class_diagram/
Lucid Software. (2023, 10 augustus). UML class diagrams [Video]. YouTube. https://www.youtube.com/watch?v=6XrL5jXmTwM
Lucid Software. (2023b, september 21). UML use case diagrams [Video]. YouTube. https://www.youtube.com/watch?v=4emxjxonNRI