How can I use composition with methods in ES6 Classes? - javascript

I am struggling to implement composition in ES6 classes! I am trying to understand a lot of things at once and have maybe made a stupid error.
I would like to avoid inheritance using extend and super for now, and have found this nice example of composition that seems to show what I am after:
class EmployeeTaxData {
constructor(ssn, salary) {
this.ssn = ssn;
this.salary = salary;
}
// ...
}
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
setTaxData(ssn, salary) {
this.taxData = new EmployeeTaxData(ssn, salary);
}
// ...
}
In regard to the code below, I would like to use the most simple and eloquent way to make the spin() method available for the objects created by the Hero class so it is using a shared prototype. I would like to share this method with other objects that will need it too.
Unfortunately I can't make my code work as the this.angle is not referring to to the Hero class that it needs to, but the Spinner class?
class Spinner {
constructor(direction){
this.direction = direction;
}
spin(direction) {
switch (direction) {
case 'left':
this.angle -= 1;
break;
case 'right':
this.angle += 1;
break;
// no default
}
}
}
class Hero {
constructor(xPosition, yPosition) {
this.description = 'hero';
this.width = 25;
this.height = 50;
this.xPosition = xPosition;
this.yPosition = yPosition;
this.angle = 0;
this.color = 'red';
this.spin = new Spinner();
}
spin() {
this.spin.spin();
}
}
const heroClass = new Hero(100, 200);
console.log(heroClass.angle); // result is 0
heroClass.spin.spin('left');
console.log(heroClass.angle); // result is STILL 0, it didn't work

...the this.angle is not referring to to the Hero class that it needs to, but the Spinner class?
As it should. When you're inside the spinner class, then this refers to a spinner object, which also means this.angle inside the spinner class refers to an angle property of a spinner object.
You'll probably want spinner to return a new angle value, then the hero object that uses spinner should save the returned new angle value.
class Spinner {
constructor(direction){
this.direction = direction;
}
spin(direction, angle) {
switch (direction) {
case 'left':
angle -= 1;
break;
case 'right':
angle += 1;
break;
// no default
}
return angle;
}
}
class Hero {
constructor(xPosition, yPosition) {
this.description = 'hero';
this.width = 25;
this.height = 50;
this.xPosition = xPosition;
this.yPosition = yPosition;
this.angle = 0;
this.color = 'red';
this.spinner = new Spinner();
}
spin(direction) {
this.angle = this.spinner.spin(direction, this.angle);
}
}
const heroClass = new Hero(100, 200);
console.log(heroClass.angle); // result is 0
heroClass.spin('left');
console.log(heroClass.angle); // result is -1
I had to make a few other small changes for this to work. For example, you had a data property named "spin" this.spin = new Spinner as well as a method named spin spin() {. They were overriding one another.

Related

Using class method to execute callback from another class instance [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 7 months ago.
class Player {
constructor() {
this.health = 100;
}
playerDamaged(applyDamage) {
applyDamage(this);
}
}
class BrickWall {
constructor() {
this.collisionDmg = 5;
}
playerCollision(player) {
player.health -= BrickWall.collisionDmg;
}
}
class SpikyWall {
constructor() {
this.collisionDmg = 25;
}
playerCollision(player) {
player.health -= SpikyWall.collisionDmg;
}
}
const player = new Player();
const brickWall = new BrickWall();
const spikyWall = new SpikyWall();
player.playerDamaged(brickWall.playerCollision);
player.playerDamaged(spikyWall.playerCollision);
console.log(player.health);
In the Player class I want to be able to pass a callback to playerDamaged to apply damage to the Player's health.
My intended objective is that line when player.playerDamaged(brickWall.playerCollision); runs, the player's health will be reduced by 5, and when player.playerDamaged(brickWall.playerCollision); runs, the player's health will be reduce by another 25.
I want console.log(player.health); to return 70. It currently returns NaN.
Thanks for any assistance you can offer!
With this
player.health -= BrickWall.collisionDmg;
you're referring to BrickWall.collisionDmg - which would only make sense if the property is on the class itself, like so:
class BrickWall {
static collisionDmg = 5;
playerCollision(player) {
player.health -= BrickWall.collisionDmg;
}
}
or
class BrickWall {
playerCollision(player) {
player.health -= BrickWall.collisionDmg;
}
}
BrickWall.collisionDmg = 5;
The class is not the instance; the instance does not inherit from the class as a prototype. (The instance inherits from the <class>.prototype, but not from <class>) So, since no property exists at BrickWall.collisionDmg in your code, it returns undefined, resulting in NaN.
Add the static properties.
class Player {
constructor() {
this.health = 100;
}
playerDamaged(applyDamage) {
applyDamage(this);
}
}
class BrickWall {
static collisionDmg = 5;
playerCollision(player) {
player.health -= BrickWall.collisionDmg;
}
}
class SpikyWall {
static collisionDmg = 25;
playerCollision(player) {
player.health -= SpikyWall.collisionDmg;
}
}
const player = new Player();
const brickWall = new BrickWall();
const spikyWall = new SpikyWall();
player.playerDamaged(brickWall.playerCollision);
player.playerDamaged(spikyWall.playerCollision);
console.log(player.health);
That said, I don't think having classes for BrickWall and SpikyWall are doing that much for you here (other than making the logic uncomfortably weird to follow due to the callbacks). A class is useful when you want to tie together data associated with an instance with methods to operate on that data - but you don't have any instance data. JS isn't Java, so don't feel like you have to use a class just because the keyword exists. Consider a plain function instead, and then call that function, perhaps like:
const brickWall = player => player.health -= 25;
or
const COLLISION_DAMAGE = {
brickWall: 5,
spikeyWall: 25,
};
// ...
player.playerDamaged(COLLISION_DAMAGE.brickWall);

p5.js Instance Mode: How to access the instance variables from inside a class

I'm having trouble using p5js in instance mode. I split my code into multiple files for easier maintenance, separating classes from the main sketch file.
Inside the classes i need access to variables declared in the main sketch but i get undefined when i inject the sketch object into the class.
Ideally i would like to have access to ANY variables declared in the main sketch file. Is it possible/recommended?
Here is a minimum case example of what i'm trying to achieve:
index.js
import p5 from "p5"
import TestClass from "./_testClass";
let sketch = new p5( (p) => {
let myColor = 75;
p.setup = function() {
p.createCanvas(1000,1000);
let t = new TestClass(100,100)
t.render(p)
}
}, "p5");
_testClass.js
export default class TestClass {
constructor(x,y) {
this.x = x;
this.y = y;
}
render(p) {
p.rect(this.x, this.y, 50, 50); // i get a square on the canvas: p.rect() is recognized
console.log(p.myColor); // undefined
}
}
Any variables that you want to be available within the TestClass's render() function either need to be 1) passed to the TestClass constructor, 2) passed to the render function, 3) declared/assigned on/to the p5 instance, or 4) declared globally.
For instance mode sketches it is not uncommon to add things to the p5 instance:
import p5 from "p5"
import TestClass from "./_testClass";
let sketch = new p5((p) => {
// let myColor = 75;
p.myColor = 75;
p.setup = function() {
p.createCanvas(1000,1000);
let t = new TestClass(100,100)
t.render(p)
}
}, "p5");
However, I think it would be better OOP practice to pass this to your TestClass constructor:
import p5 from "p5"
import TestClass from "./_testClass";
let sketch = new p5((p) => {
let myColor = 75;
p.setup = function() {
p.createCanvas(1000, 1000);
let t = new TestClass(100, 100, myColor)
t.render(p)
}
}, "p5");
export default class TestClass {
constructor(x, y, rectColor) {
this.x = x;
this.y = y;
this.rectColor = rectColor;
}
render(p) {
console.log(this.rectColor);
p.fill(this.rectColor);
p.rect(this.x, this.y, 50, 50);
}
}

how to prevent overlapping of 2 components

how to prevent overlapping of 2 components, please help me to make them follow Mouse but not overlap. i am not expert in coding, please explain in simple language.
function component(x,y,r) {
var randomcolor = ["violet","indigo","blue","green","yellow","orange","red"];
this.pos=createVector(x,y);
this.r=r;
this.color=randomcolor[Math.floor(Math.random()*randomcolor.length)];
this.show=function() {
fill(this.color);
stroke(241,241,241);
ellipse(this.pos.x,this.pos.y,this.r*2,this.r*2);
}
this.crash = function(other) {
var d = p5.Vector.dist(this.pos,other.pos);
if (d<this.r+other.r) {
this.r+=other.r/20;
return true;}
}
this.move=function(){
this.pos.x=lerp(this.pos.x,mouseX,0.1);
this.pos.y=lerp(this.pos.y,mouseY,0.1);
this.pos.x = constrain(this.pos.x,this.r,width-this.r)
this.pos.y = constrain(this.pos.y,this.r,height-this.r)
}
}
To make multiple objects move without running into each other you will need to
keep track of the current location of all objects
be able to identify each object so that the collision method does not detect a collision of an object with itself
check to make sure there will not be a collision before attempting to move an object
For your example code this is one possibility for making multiple components move towards the mouse without running into each other. I rewrote your crash function and added some global variables. This is not elegant but I think it answers your question in a way that you can understand how this kind of problem can be approached.
var ids = 0;
var allComponents = [];
function setup(){
createCanvas(600,600);
new component(10,10,10);
new component(590,10,10);
}
function draw(){
background(255);
for (let i = 0; i < allComponents.length; i++){
allComponents[i].show();
allComponents[i].move();
}
}
function component(x,y,r) {
var randomcolor = ["violet","indigo","blue","green","yellow","orange","red"];
this.pos=createVector(x,y);
this.r=r;
this.id = ids++;
allComponents[allComponents.length] = this;
this.color=randomcolor[Math.floor(Math.random()*randomcolor.length)];
this.show=function() {
fill(this.color);
stroke(241,241,241);
ellipse(this.pos.x,this.pos.y,this.r*2,this.r*2);
}
this.crash = function(other) {
var d = p5.Vector.dist(this.pos,other.pos);
if (d< this.r + other.r) {
return true;
}
return false;
}
this.move=function(){
let originalX = this.pos.x;
let originalY = this.pos.y;
this.pos.x=lerp(this.pos.x,mouseX,0.1);
this.pos.y=lerp(this.pos.y,mouseY,0.1);
this.pos.x = constrain(this.pos.x,this.r,width-this.r);
this.pos.y = constrain(this.pos.y,this.r,height-this.r);
for (let i = 0; i < allComponents.length; i++){
let other = allComponents[i];
if (this.id !== other.id && this.crash(other)){
this.pos.x = originalX;
this.pos.y = originalY;
break;
}
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>

Type Error - Referencing Javascript Classes

Firstly a little elaboration of the project I'm working on. I have started building a 'map maker' for a 2d game I am working on. The project is just for fun and has proven so far to be a great way to learn new things.
I recently showed my working map maker code to a friend who suggested it would be much more re-usable if I restructured the project to be more OOR, which I am now attempting.
The problem I have is when I add a 'Guild' instance to my map, the first one works fine, but the second causes a type error that is giving me a headache!
I will post all of the relevant code from the different files below, but the overall structure is as follows:
Map.js = Map class file, container for setting the map overall size and iterating over (and placing) map objects.
MapObject.js = Class file for simple map objects such as walls, contains the position and icon properties.
Guild.js = Class file, extends MapObject.js, this is where my problem seems to be, adds an additional 'MapIcon' and will have other features such as levels and names etc.
map-maker.js = Main file for generating the map-maker page, utilises the above class files to create the map.
Below is the code used to create an instance of 'Guild' on my map:
map-maker.js (creating the map / map object / guild instances)
// Initialise a new instance of map class from Map.js using the user
provided values for #mapX and #mapY.
var currentMap = new Map(XY,40);
// Get the user box volume * map boxsize from Map.js
currentMap.calcDimensions();
// Create a Map Object and push it to the currentMap with its position.
function createMapObject(x,y,floor){
currentMap.objects.push(new MapObject(x,y,floor));
}
// Create a Guild Object (extension of Map Object) and push it to the currentMap with its position.
function createGuildObject(x,y,floor){
currentMap.objects.push(new Guild(x,y,floor));
}
....
case 13: // Enter Key (Submit)
unhighlightTools();
currentMap.drawMap();
if(currentFloor != null){
currentFloor.hasFloor = true;
if(currentFloor.tileName == "Guild"){
createGuildObject(currentFloor.position.x,currentFloor.position.y,currentFloor);
}else {
createMapObject(currentFloor.position.x,currentFloor.position.y,currentFloor);
}
console.log("Map object created at - X:"+currentFloor.position.x+" Y:"+currentFloor.position.y);
}
currentFloor = [];
highlightTools();
break;
}
Guild.js (constructor and assigning map icon)
class Guild extends MapObject {
constructor(x,y,floor) {
super(x,y,floor);
this.levels = [];
}
mapIcon() {
this.mapIcon = new Image();
this.mapIcon.src = "../images/mapSprites/obj-1.png"
return this.mapIcon;
}
}
MapObject.js (position setup and constructor)
class MapObject {
constructor(x,y,floor) {
this.position = {x, y};
this.icon = this.wallFloorIcons(floor);
}
wallFloorIcons(floor) {
this.img = new Image();
this.name = "";
this.name += (floor.wallNorth) ? 'n' : '';
this.name += (floor.wallEast) ? 'e' : '';
this.name += (floor.wallSouth) ? 's' : '';
this.name += (floor.wallWest) ? 'w' : '';
this.name = 'wall-'+this.name+'.png';
if(this.name == 'wall-.png'){
this.img.src = "../images/mapSprites/floor.png";
}else {
this.img.src = "../images/mapSprites/"+this.name;
}
return this.img;
}
getIcon() {
return this.img;
}
}
Map.js (processing the objects at a given location and drawing the canvas)
class Map {
// Map Width / Height and number of boxes. Used to generate map and zoom level.
constructor(wh, boxSize) {
this.size = wh;
this.width = wh[0];
this.height = wh[1];
this.boxSize = boxSize;
this.objects = [];
this.boxes = wh[0];
}
// Calculates the width and height * boxSize for rendering the canvas.
calcDimensions(){
this.realX = Math.floor(this.width * this.boxSize);
this.realY = Math.floor(this.height * this.boxSize);
this.realX = parseInt(this.realX,10);
this.realY = parseInt(this.realY,10);
this.realXY = [
this.realX,
this.realY
];
return this.realXY;
}
// Draws the canvas, grid and adds the objects that belong to the map.
drawMap(){
var self = this;
self.canvas = document.getElementById("canvas");
self.c = self.canvas.getContext("2d");
self.background = new Image();
self.background.src = "../images/mapSprites/oldPaperTexture.jpg";
// Make sure the image is loaded first otherwise nothing will draw.
self.background.onload = function(){
self.c.drawImage(self.background,0,0);
self.fillMap();
}
}
fillMap(){
var self = this;
self.c.lineWidth = 1;
self.c.strokeStyle = 'black';
self.c.fillStyle = "rgba(255, 255, 255, 0.2)";
for (var row = 0; row < self.boxes; row++) {
for (var column = 0; column < self.boxes; column++) {
var x = column * self.boxSize;
var y = row * self.boxSize;
self.c.beginPath();
self.c.rect(x, y, self.boxSize, self.boxSize);
self.c.stroke();
self.c.closePath();
for (var i=0; i<self.objects.length; i++) {
var floor = self.objects[i];
if (floor.position.x == column && floor.position.y == row) {
if (self.objectsAtPosition({x:floor.position.x, y:floor.position.y}) != null) {
var mapObjects = self.objectsAtPosition({x:floor.position.x, y:floor.position.y})
for (var mapObject of mapObjects) {
this.c.drawImage(mapObject.getIcon(), x, y, self.boxSize, self.boxSize);
console.log(mapObject);
if(mapObject instanceof Guild){
console.log(mapObject);
this.c.drawImage(mapObject.mapIcon(), x, y, self.boxSize, self.boxSize);
}
}
}
}
}
}
}
}
deleteObject(pos){
this.objectsAtPosition(pos);
for( var i = 0; i < this.objects.length; i++){
if(this.objects[i] == this.objs){
delete this.objects[i];
this.objects.splice(i,1);
}
}
}
objectsAtPosition(position) {
var objs = [];
for (var o of this.objects) {
if (o.position.x == position.x && o.position.y == position.y) {
objs.push(o);
}
}
return objs;
}
}
When I run the code, this is my error:
Uncaught TypeError: mapObject.mapIcon is not a function
at Map.fillMap (Map.js:70)
at Image.self.background.onload (Map.js:39)
The error comes after I add 1 guild then try to add any other map object. Guild or otherwise.
Sorry if this question is a little vague, I'm still learning (as you can see :p).
Thanks for your time!
Earl Lemongrab
Got a solution from a friend in the end.
The issue was that I was reassigning this.mapIcon = new Image() so it exploded when it was called a second time.
Feel pretty silly for not spotting it.
Thanks for the help everyone.

Uncaught type Error: cannot read property 'x' of undefined

Can someone tell me where I went wrong? I have been looking at this for a hour and i can't see why it is not this.x is undefined.
it is somewhere here:
var canvas, canvasContext;
var blueCar = new carClass();
window.onload = function() {
canvas = document.getElementById('gameCanvas');
canvasContext = canvas.getContext('2d');
colorRect(0,0,canvas.width,canvas.height,'red');
colorText(" LOADING...",canvas.width/3,canvas.height/2,'white',"30px Arial")
loadImages();
}
function imageLoadingDoneSoStartGame(){
var framesPerSecond = 30;
setInterval(updateAll, 1000/framesPerSecond);
setUpInput();
bluecar.reset();
}
function updateAll() {
moveAll();
drawAll();
}
function moveAll() {
blueCar.move();
carTrackHandling();
}
function drawAll() {
console.log(bluecar.x);
drawTracks();
blueCar.draw(blueCar);
}
here:
function carClass(){
this.x = 75;
this.y = 75;
this.ang = 0;
this.speed = 0;
//reset car 1
this.reset = function() {
for(var eachRow=0;eachRow<TRACK_ROWS;eachRow++) {
for(var eachCol=0;eachCol<TRACK_COLS;eachCol++) {
var arrayIndex = rowColToArrayIndex(eachCol, eachRow);
if(trackGrid[arrayIndex] == TRACK_BLUE_START) {
trackGrid[arrayIndex] = TRACK_ROAD;
this.ang = -Math.PI/2;
this.x= eachCol * TRACK_W + TRACK_W/2;
this.y= eachRow * TRACK_H + TRACK_H/2;
}//end of player start if
}//end if col for
}//end if row for
}//end of car reser function
//move car 1
this.move = function() {
this.speed *= GROUND_SPEED_DECAY_MULT
if(keyHeld_Gas){
this.speed += DRIVE_POWER;
}
if(keyHeld_Reverse){
this.speed -= REVERSE_POWER;
}
if(Math.abs(this.speed) > MIN_SPEED_TO_TURN){
if(keyHeld_TurnLeft){
this.ang -= TURN_RATE;
}
if(keyHeld_TurnRight){
this.ang += TURN_RATE;
}
}
this.x += Math.cos(this.ang) * this.speed;
this.y += Math.sin(this.ang) * this.speed;
// carAng += 0.02
}
this.draw = function(){
drawBitmapCenteredWithRoatation(blueCarPic, this.x,this.y, this.ang);
}
}
or here:
function carTrackHandling(wichCar) {
var carTrackCol = Math.floor(wichCar.x / TRACK_W);
var carTrackRow = Math.floor(wichCar.y / TRACK_H);
var trackIndexUnderCar = rowColToArrayIndex(carTrackCol, carTrackRow);
if(carTrackCol >= 0 && carTrackCol < TRACK_COLS &&
carTrackRow >= 0 && carTrackRow < TRACK_ROWS) {
if(isObstacleAtColRow( carTrackCol,carTrackRow )) {
//fixes bug
wichCar.x -= Math.cos(wichCar.ang) * wichCar.speed;
wichCar.y -= Math.sin(wichCar.ang) * wichCar.speed;
//reduse speed and bounces car when hits a wall
wichCar.speed*= -0.5;
} // end of track found
} // end of valid col and row
} // end of carTrackHandling func
the exact error i am getting is:
where Track.js: is : var carTrackCol = Math.floor(wichCar.x / TRACK_W);
Uncaught TypeError: Cannot read property 'x' of undefined
at carTrackHandling (Track.js:34)
at moveAll (Main.js:29)
at updateAll (Main.js:23)
any help is appreciated in finding it, and telling me what I did wrong thankyou.
Your function is defined as function carTrackHandling(wichCar) {. It takes one parameter, which is the car that (I assume) you want to handle.
If your moveAll() function, you're not passing in a car to your function. When your function tries to access x of whichCar it throws an error because it's undefined (As it was not passed any car). Pass the car you want to handle as shown below in your moveAll() function:
carTrackHandling();
It should be:
blueCar.move();
carTrackHandling(blueCar);
It doesn't look like you are passing anything to carTrackHandeling() and it is expecting to be passed an object.
It looks like you are calling it here with no parameters.
function moveAll() {
blueCar.move();
carTrackHandling();
}
Then when you make it to this function whichCar would be undefined causing the error you are seeing.
function carTrackHandling(wichCar) {
var carTrackCol = Math.floor(wichCar.x / TRACK_W);
...
}

Categories

Resources