Getting object fields and calling object functions in JavaScript - javascript

I just started fiddling around with JavaScript. Coming from Java and OO PHP things are getting weirder with every step :)
This is my introduction project to javascript in which I've set out to program multiplayer working version of Settlers of Catan. Code below is an attempt to store cube coordinates of N sized hexagonal map tiles in an array.
I've read you declare object in javascript by assigning functions to variables.
var Tile = function (x, y, z) {
this.x = x;
this.y = y;
this.z = z;
};
var Map = function () {
var grid = [];
function generate_map(radius) {
for (width = -radius; width <= radius; width++) {
var r1 = Math.max(-radius, -width - radius);
var r2 = Math.min(radius, -width + radius);
for (r = r1; r <= r2; r++) {
grid.push(new Tile(width, r, -width - r));
}
}
}
};
I've tried instantiating new Map object, calling its only function and outprinting the resulting values stores in grid[] array. But for each loop is not playing nice :( I get the unexpected identifier.
var main = function () {
var basic_map = new Map();
basic_map.generate_map(3);
for each (var tile in basic_map.grid) {
console.log(tile.x, tile.y, tile.z);
}
};
main();
I am fully aware this is one of those face palm errors, but help would nevertheless be appreciated, cheers!

Change this:
function generate_map(radius) {
...to this:
this.generate_map = function(radius) {
Edit: there are actually more issues than I at first realized.... :)
A few other tips:
First, I would recommend changing:
var Tile = function (x, y, z) {
...to simply be:
function Tile(x, y, z) {
(the same goes for Map). Your current solution works fine, but it's not very idiomatic, and until ES6 there was nothing in the spec that would cause var Tile = function to cause the resulting function's 'name' property to be set to "Tile", which is useful when it comes to debugging. I recently wrote another answer that delves a bit more into the differences between, e.g., function Foo() {} and var Foo = function() {}.
Second, you probably want to rename Map to something else. Map is a core part of ES6 now (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map).
Third, even though you can create your generate_map function using this.generate_map, you may want to move it to the Map's prototype. Also, since you need to expose the grid value, you want to store it as a property, rather than a local variable scoped to the NewMapName constructor. E.g.,:
function NewMapName() {
this.grid = [];
}
NewMapName.prototype.generateMap = function(radius) {
// you can access the grid here via `this.grid`
...
};
By moving it to the prototype, that means all instances of NewMapName will share the same function reference, rather than it being created over-and-over-and-over (although maybe you really only create it once? Either way, it's more idiomatic, at a minimum). Note that I took some liberties with the "camelCasing" here (see the last point).
Fourth, your generateMap implementation is leaking some global variables (width and r, since you don't declare them with var). I would change that to this:
NewMapName.prototype.generateMap = function(radius) {
for (var width = -radius; width <= radius; width++) {
var r1 = Math.max(-radius, -width - radius);
var r2 = Math.min(radius, -width + radius);
for (var r = r1; r <= r2; r++) {
grid.push(new Tile(width, r, -width - r));
}
}
};
Fifth, your loop is kind of broken. I would refactor that as follows:
var main = function () {
var basicMap = new NewMapName();
basicMap.generateMap(3);
basicMap.grid.forEach(function(tile) {
console.log(tile.x, tile.y, tile.z);
});
};
main();
Lastly, and probably most minor, is that in JavaScript-land, camelCase is far more dominant that snake_case, so generate_map might be "better" as generateMap.

Related

Adds as string instead of number

When I add two variables that I initialize as numbers, JS considers them as a string and concatenates them. In a calculation as follows,
var p1 = new window.TRIGEO.Point(150, 150);
var p2 = new window.TRIGEO.Point(500, 350);
var p3 = new window.TRIGEO.Point(50, 500);
var medicentre = new Point((p1.x+p2.x+p3.x)/3,(p1.y+p2.y+p3.y)/3);
(where Point has x and y as members),medicentre is huge =>( 5016683.33 , 50116833.33 ). I do not want this when the answer is actually =>( 233.33 , 333.33 ).
Is there any way to override this behaviour without making the formula too long, cause I have another one, which is at least three lines long. Is this possible without using parseInt()?
EDIT:
Point object is the following and that's it!
function Point(x, y) {
this.x = x;
this.y = y;
}
TRIGEO is the name of the library I'm writing to visualize all the important points and line segments of a triangle. Sorry for the confusion, I probably should've edited.
whilst Parsint as already answered is technically correct, if you want a shorter formula you can trick the casting by making your values positive (as long as you know they aren't going to have non-numberic values
var num1 = '1111.11';
var num2 = 2222.22;
var n1plus2 = (+num1)+num2
// n1p2 : 3333.33
or both strings:
var num1 = '1111.11';
var num2 = '2222.22';
var n1p2 = (+num1)+(+num2);
// n1p2 : 3333.33
JavaScript doesn't consider them as string. You probably execute an operation in the Point class and convert them.
function A(x, y) {
this.x = x;
this.y = y;
}
var a = new A(1, 1),
b = new A(2, 2),
c = new A(a.x + b.x, a.y + b.y);
alert(c.x + " - " + c.y)
This will output 3 - 3 as expected.

Where are the arguments critter & vector defined in Eloquent JavaScript Chapter 7

Working slowly through the Eloquent JavaScript book by Marijn Haverbeke and am trying to get my head around the first step of the World.turn() function:
World.prototype.turn = function() {
var acted = [];
this.grid.forEach(function(critter, vector) {
if (critter.act && acted.indexOf(critter) == -1) {
acted.push(critter);
this.letAct(critter, vector);
}
}, this);
};
Where does the prototype forEach function get the arguments critter and vector from.
When I log their output to the console, I get an object with the originChar and a direction if valid, but can't get my head around where it is getting the arguments.
The link to the section is as follows: http://eloquentjavascript.net/07_elife.html#h_6OGIzAd5Tr
Thanks in advance :)
The World owns a Grid, which has forEach defined on it just above the section you're looking at.
Grid.prototype.forEach = function(f, context) {
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var value = this.space[x + y * this.width];
if (value != null)
f.call(context, value, new Vector(x, y));
}
}
};
The grid's cells contain critters, which you see captured in var value = this.space[x + y * this.width] and passed to the function you provide forEach, along with new Vector(x, y) containing the location in the grid.

GreenSock, tween function arguments

I got two functions called on mouse events:
function menuBtnOver(e){
var b = e.data;
b.setPosition(b.x, b.y+5);
}
function menuBtnOut(e){
var b = e.data;
b.setPosition(b.x, b.y-5);
}
and:
setPosition:function(x, y) {
if(!x) x = 0;
if(!y) y = 0;
this.element.css("left", x);
this.element.css("top", y);
}
element property is a jQuery object.
It is working ok but i want to animate this. How can i do this with TweenLite?
I've tried following code:
function menuBtnOver(e){
TweenLite.to(e.data, 1, {top:500});
}
As well as this:
function menuBtnOver(e){
TweenLite.to(e.data.getElement(), 1, {top:500});
}
and many other combinations but none of them worked.
Only on method which partially work is this:
function menuBtnOver(e){
TweenLite.to(e.data, 1, {y:400, onUpdate:e.data.setPosition, onUpdateParams:[e.data.x, e.data.y]});
}
But it work only on fist button when I roll over and (after any time) roll out, it moves directly to given position (without tween) and then the tween goes forever giving me error each time(at least - I couldn't get any errors or anything with other attempts).
Uncaught TypeError: Cannot read property 'css' of undefined
at: this.element.css("left", x);
Update
I figured out what is going on.
I've changed code as so:
function menuBtnOver(e){
TweenLite.to(e.data, 1, {y:400, onUpdate:e.data.setPosition, onUpdateParams:[e.data.x, e.data.y], onUpdateScope:e.data});
}
But the problem with this is that arguments to update function which I set to e.data.y/x aren't dynamic references and always stay as those exact values from menuBtnOver state. So the tween works if i change setPosition function to:
setPosition:function(x, y) {
if(!x) x = 0;
if(!y) y = 0;
this.element.css("left", this.x);
this.element.css("top", this.y);
}
But obviously this is not what I want to do.
So I have option to make something like this:
MenuButton.prototype = {
setPosition:function(x, y) {
if(!x) x = 0;
if(!y) y = 0;
this.x = x; this.y = y;
this.element.css("left", x);
this.element.css("top", y);
},
updatePosition:function(){
this.element.css("left", this.x);
this.element.css("top", this.y);
}
}
function menuBtnOver(e){
TweenLite.to(e.data, 1, {y:400, onUpdate:e.data.updatePosition, onUpdateScope:e.data});
}
Or define external update function in similar manner. The question still stays the same so is there a simple way to do this simpler. Does GS tween has any mechanic which automate this process?
Thanks to everyone for attention :)
this in setPosition is referring to that function and not the this of the onClick event.
you need to do pass this to setPosition. As in the example below, where I passed it as self in the function setPosition.
function menuBtnOver(e){
var b = e.data;
b.setPosition(this, b.x, b.y+5);
}
function menuBtnOut(e){
var b = e.data;
b.setPosition(this, b.x, b.y-5);
}
and:
setPosition:function(self, x, y) {
if(!x) x = 0;
if(!y) y = 0;
self.element.css("left", x);
self.element.css("top", y);
}
this always refernces the function in which is was called. as you can read about her. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
So you can pass this in a function as a variable.

Javascript oop dubt

I made this two classes below in the code, and I am not sure if I made it in a right oop way. Is it good that I made geometry class and vertex like two distinct classes or maybe they can be one father and child? Another problem is when I call geometry show method and it returns me undefined.
//////////////////////////////////////////
// VERTICES
//////////////////////////////////////////
function Vertex(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
Vertex.prototype.show = function () {
return this.x + ":" + this.y + ":" + this.z;
}
//////////////////////////////////////////
// GEOMETRY
//////////////////////////////////////////
function Geometry() {
this.vertices = [];
}
Geometry.prototype.push = function(v) {
this.vertices.push(v);
}
Geometry.prototype.show = function() {
for(var i = 0; i < this.getVertexCount(); i++){
this.vertices[i].show();// undefined!
}
}
Geometry.prototype.getVertexCount = function() {
return this.vertices.length;
}
/////TEST/////
function test() {
v = new Vertex(2,4,6);
console.log(v.show());
g = new Geometry();
g.push(v);
console.log(g.show()); //undefined
}
I am not sure if I made it in a right oop way.
Seems fine, I can't see any common mistakes.
My doubt is for geometry class that has a vertice object field inside. Is it correct or the are better way to do it?
Depends on what you need. There's nothing inherently wrong with it, but if you told us your use case we might find a different solution.
Is it good that I made geometry class and vertex like two distinct classes or maybe they can be one father and child?
No, there should not be any inheritance. There is no is-a relationship between them. They should be distinct classes, one using the other.
Another problem is when I call geometry show method and it returns me undefined.
Yes, because it doesn't return anything. All those strings that it gets from the invocation of the Vertice show() calls are thrown away. It seems like you want something like
Geometry.prototype.show = function() {
var result = "";
for (var i = 0; i < this.getVertexCount(); i++) {
if (i > 0)
result += "\n";
result += this.vertices[i].show();
}
return result; // not undefined!
}

Garbage collection pauses; Javascript

I've been working on creating a basic 2D tiled game and have been unable to pinpoint the source of noticeable pauses lasting ~100-200ms every second or two, but it seems like GC pauses as when I profiled my app, each game loop is taking around 4ms with a target of 60fps, which means it is running well within the required limit (16ms).
As far as I am aware, I have moved my object variables outside the functions that use them so they never go out of scope and therefore should not be collected, but I am still getting pauses.
Each game loop, the tiles are simply moved 1px to the left (to show smoothness of game frames), and apart from that, all that is called is this draw map function: (NOTE, these functions are defined as part of my engine object at startup so is this true that these functions are not created then collected each time they are called?).
engine.map.draw = function () {
engine.mapDrawMapX = 0;
engine.mapDrawMapY = 0;
// Just draw tiles within screen (and 1 extra on both x and y boundaries)
for (engine.mapDrawJ = -1; engine.mapDrawJ <= engine.screen.tilesY; engine.mapDrawJ++) {
for (engine.mapDrawI = -1; engine.mapDrawI <= engine.screen.tilesX; engine.mapDrawI++) {
//calculate map location (viewport)
engine.mapDrawMapX = engine.mapDrawI + engine.viewport.x;
engine.mapDrawMapY = engine.mapDrawJ + engine.viewport.y;
engine.mapDrawTile = (engine.currentMap[engine.mapDrawMapY] && engine.currentMap[engine.mapDrawMapY][engine.mapDrawMapX]) ? engine.currentMap[engine.mapDrawMapY][engine.mapDrawMapX] : '';
engine.tile.draw(engine.mapDrawI, engine.mapDrawJ, engine.mapDrawTile);
}
}
};
And the method called to draw each tile is:
engine.tile.drawTile = new Image(0,0);
engine.tile.draw = function (x, y, tile) {
if ('' != tile) {
engine.tile.drawTile = engine.tile.retrieve(tile); //this returns an Image() object
engine.context.drawImage(engine.tile.drawTile,
x * TILE_WIDTH + engine.viewport.offsetX,
y * TILE_HEIGHT + engine.viewport.offsetY,
TILE_WIDTH, TILE_HEIGHT);
} else {
engine.context.clearRect(x * TILE_WIDTH, y * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT);
}
};
As per request, here are the store and retrieve functions:
engine.tile.store = function (id, img) {
var newID = engine.tile.images.length;
var tile = [id, new Image()];
tile[1] = img;
engine.tile.images[newID] = tile; // store
};
engine.tile.retrieveI;
engine.tile.retrieve = function (id) {
//var len = engine.tile.images.length;
for (engine.tile.retrieveI = 0; engine.tile.retrieveI < engine.tile.images.length; engine.tile.retrieveI++) {
if (engine.tile.images[engine.tile.retrieveI][0] == id) {
return engine.tile.images[engine.tile.retrieveI][1]; // return image
}
}
//return null;
};

Categories

Resources