Javascript array of canvas objects? Scope issue? ocanvas.js - javascript

I'm new to javascript and canvas, but not entirely new to programming (I have some HTML/PHP experience).
I've always had an interest in web games that use grids, so after taking a few javascript courses online, I found some javascript code that dynamically created hexagonal grids of canvas objects.
I've been playing around with the code, adding click events to highlight the hex you're on, trying to put random numbers on the hexes to depict numbers of "units" etc. Sort of mocking up what a game might look like, just for the learning experience.
One thing that I've been trying to do, and I can't seem to figure out, is how to define canvas objects in an array, so that I can reference them by row/column. It seems like it would be much easier, in the context of a game, to reference a grid cell by row/column so that you can do actions based on that location.
I'm using ocanvas.js to create canvas objects, so ideally I'd love to be able to define a grid cell as:
hex[row][col] = canvas.display.polygon({
x: x0,
y: y0,
sides: 6,
radius: this.radius,
rotation: 0,
fill: fillColor,
stroke: "outside 1px #000",
row: row,
col: col,
zIndex: "back",
selected: false
});
hex[row][col].add();
However, I've noticed that mutlidimensional arrays don't work in javascript like they do in PHP. In PHP, you don't have to predefine the scope of an array. You can just do:
$array = [];
$array[1][5] = "foo";
And it'll put that value in the [1][5] position. In my hex code here: http://tny.cz/14282e49 about 3/4 down I have a comment showing that I want to call my hex[col][row] object... but I'm always getting "Uncaught TypeError: Cannot read property '5' of undefined" errors, where '5' is the column its trying to access.
So, this is a 2 part question:
1) Am I not defining my hex array correctly to access the canvas objects like that?
2) Is there a better way that I'm just not seeing, to access specific canvas objects in a column/row grid format?

Part I:
Before assigning a value to hex[row][col], you need to make sure that hex[row] exists.
hex[row] = hex[row] || [];
hex[row][col] = canvas.display.polygon({
x: x0,
y: y0,
sides: 6,
radius: this.radius,
rotation: 0,
fill: fillColor,
stroke: "outside 1px #000",
row: row,
col: col,
zIndex: "back",
selected: false
});
hex[row][col].add();
The line hex[row] = hex[row] || []; says "if hex[row] exists, use it. otherwise create a new array. Assign the result to hex[row]."
The || "or" operator is used as a "default" here. See this page as a reference: http://seanmonstar.com/post/707078771/guard-and-default-operators
Part II:
http://www.redblobgames.com/grids/hexagons/#coordinates
I recommend using the "axial coordinate" system used at that resource. Read the entire thing. It's a long post and is way too much information for a SO answer here.

Related

Name Firebase child as sequence array

I want to use Fusionchart to read my Firebase data and create a chart in my web app. but my Firebase DB has a wrong structure, so the Fusionchart can't get data (my Firebase config is right).
Following is the code that I write data to Firebase, num is a value increased in each loop. But as shown in the attached picture, the child's name is not added as a sequence number.
Another question is I don't want the unique key inside the child 1, just six various inside the child one is ok.
Any suggestions will be appreciated.
firebase.database().ref('testdata/User1').child(num).push({
x: posX,
y: posY,
MaxSpeed: maxSpeed,
steps: counter,
time: Timeperiod /1000,
speed: SpeedRecord,
});
If you don't want the push ID inside your pseudo-numeric keys, call set instead of push.
So:
firebase.database().ref('testdata/User1').child(num).set({
x: posX,
y: posY,
MaxSpeed: maxSpeed,
steps: counter,
time: Timeperiod /1000,
speed: SpeedRecord,
});
Your other problems seems (it's impossible to be certain, since you didn't include the code for the increment) to come from the fact that num is a string. If that is indeed the case, increment it with:
num = String(parseInt(num) + 1);
Using such numeric keys is an antipattern in Firebase though, so I'd usually recommend against using them. If you must, at least pad them til a certain length, so that you can sort/filter on them easily.
Something as simple as:
num = String(parseInt(num) + 1).padLeft(5, "0");
Will work on all modern browsers, and ensures that all keys-that-look-like-numbers-but-behave-like-strings will show up in the order you expect.

How can I generate a set of coordinates for a circle that avoids serveral specified areas?

I'm working on a 2D game, and the safespawn isn't great. The current system gets a random coordinate and checks for collision, trying again if the position collides with another object. After 15 failed attempts, it will give up and spawn on top of another object. I am wondering if there is a way to create a formula to calculate a position outside of an array of positions. For example, say I have this array:
var coveredArea = [
{x: 200, y: 100, r: 50}
{x: 100, y: 300, r: 50}
{x: 300, y: 200, r: 50}
];
For a visual representation, let's say the canvas looks like this: https://i.imgur.com/ZfPPL4P.png
I want to make a formula to calculate a position outside of these objects without random guess and check. Is this possible? I would hope to get a base idea here that will steer me in the right direction if a solution is too complex to be given away.
If scene dimensions are resonable, prohibited objects do not vary position frequently and you have to execute many queries, the simplest and fastest is map approach:
Make bitmap, fill it by black and draw all the objects filled with white color (if you need to distinguish different objects, draw each by specific color #000001, #000002 etc).
Then just check for pixel color in needed coordinates.
If you have specific radius of circle (not just point), then use wide pen for figure borders (lineWidth =2*R+1, lineJoin = round)

Using plotly.js animation feature

Yesterday Plotly release the new feature animation!!! So I was very eager to test this out, and with the current lack of documentations (temporary I suppose) I'm struggling quite a bit on how to use it.
I did have a peek into the code on GitHub, but ... not working.
I define my div element in the template:
<div id="plotDiv"> </div>
I wanted to have the plot responsive to resize events, thus I followed the example on the plotly website:
const d3 = Plotly.d3;
const gd3 = d3.select("#plotDiv")
.style({width: "95%", "margin-left": "2.5%"});
const gd = gd3.node();
Then generate the data (bits of angular magic and other things) but in the end it looks like this:
data = {x: [array_ot_dates], y: [array_of_not_so_random_values], type:'bar'};
According to the jsdocs for the animation function, you need to pass a frame:
let plotlyFrame = {data:data, layout:{}};
Try to call animation!!!
Plotly.animate(gd, plotlyFrame);
And there it goes Kaboum!
First error is: This element is not a Plotly plot: [object HTMLDivElement]
But when I try this:
Plotly.newPlot(gd, data, {});
I have my plot...
So I tried to "predefine" gd by calling Plotly.plot with empty data and then the animate function...
Plotly.plot(gd, [], {});
// make my data not empty
Plotly.animate(gd, plotlyFrame);
And then I get the following error:
plotly.js:116922 Uncaught (in promise) TypeError: Cannot read property '_module' of undefined(…)
Possibly the second could come from the fact I'm using angular and thus calling the function at one point 3 times in a close row.
Any advices? Ideas?
I'm the person who worked on the animation feature! First of all, you can find the documentation here.
Regarding your specific question, it looks like the answer is that you need to initialize the plot before you animate it (I'll add this to the documentation!). For example:
var frame = [{
data: {
y: [...new values...]
}
}]
Plotly.plot(gd, [{x: [...], y: [...]}]).then(function() {
Plotly.animate(gd, frame)
});
If some sort of user input is triggering the animation, the promise probably isn't necessary (since the input event will handle things and is pretty unlikely to get fired before Plotly.plot has had a chance to initialize).
Additionally, at the very least I believe you'll need to actually initialize the plot with the trace you wish to animate. I think you can probably get away with empty data, but you'll still need to at least have a trace defined, e.g. Plotly.plot(gd, [{x: [], y: []}]). I think the issue with your attempted fix is that [] for a list of traces is still empty as far as Plotly is concerned since that tells it nothing about the type of trace that exists. Also FYI, one thing the feature is not really designed to do is to draw the initial plot in a fancy manner. That could likely be accomplished, but it's not automatic given animate on an empty plot.
I hope that's enough to clarify issues! It was a pretty large feature, so we'd love feedback and to hear about successes/failures with it!

render a tile map using javascript

I'm looking for a logical understanding with sample implementation ideas on taking a tilemap such as this:
http://thorsummoner.github.io/old-html-tabletop-test/pallete/tilesets/fullmap/scbw_tiles.png
And rendering in a logical way such as this:
http://thorsummoner.github.io/old-html-tabletop-test/
I see all of the tiles are there, but I don't understand how they are placed in a way that forms shapes.
My understanding of rendering tiles so far is simple, and very manual. Loop through map array, where there are numbers (1, 2, 3, whatever), render that specified tile.
var mapArray = [
[0, 0, 0, 0 ,0],
[0, 1, 0, 0 ,0],
[0, 0, 0, 0 ,0],
[0, 0, 0, 0 ,0],
[0, 0, 1, 1 ,0]
];
function drawMap() {
background = new createjs.Container();
for (var y = 0; y < mapArray.length; y++) {
for (var x = 0; x < mapArray[y].length; x++) {
if (parseInt(mapArray[y][x]) == 0) {
var tile = new createjs.Bitmap('images/tile.png');
}
if (parseInt(mapArray[y][x]) == 1) {
var tile = new createjs.Bitmap('images/tile2.png');
}
tile.x = x * 28;
tile.y = y * 28;
background.addChild(tile);
}
}
stage.addChild(background);
}
Gets me:
But this means I have to manually figure out where each tile goes in the array so that logical shapes are made (rock formations, grass patches, etc)
Clearly, the guy who made the github code above used a different method. Any guidance on understanding the logic (with simply pseudo code) would be very helpful
There isn't any logic there.
If you inspect the page's source, you'll see that the last script tag, in the body, has a huge array of tile coordinates.
There is no magic in that example which demonstrates an "intelligent" system for figuring out how to form shapes.
Now, that said, there are such things... ...but they're not remotely simple.
What is more simple, and more manageable, is a map-editor.
Tile Editors
out of the box:
There are lots of ways of doing this... There are free or cheap programs which will allow you to paint tiles, and will then spit out XML or JSON or CSV or whatever the given program supports/exports.
Tiled ( http://mapeditor.org ) is one such example.
There are others, but Tiled is the first I could think of, is free, and is actually quite decent.
pros:
The immediate upside is that you get an app that lets you load image tiles, and paint them into maps.
These apps might even support adding collision-layers and entity-layers (put an enemy at [2,1], a power-up at [3,5] and a "hurt-player" trigger, over the lava).
cons:
...the downside is that you need to know exactly how these files are formatted, so that you can read them into your game engines.
Now, the outputs of these systems are relatively-standardized... so that you can plug that map data into different game engines (what's the point, otherwise?), and while game-engines don't all use tile files that are exactly the same, most good tile-editors allow for export into several formats (some will let you define your own format).
...so that said, the alternative (or really, the same solution, just hand-crafted), would be to create your own tile-editor.
DIY
You could create it in Canvas, just as easily as creating the engine to paint the tiles.
The key difference is that you have your map of tiles (like the tilemap .png from StarCr... erm... the "found-art" from the example, there).
Instead of looping through an array, finding the coordinates of the tile and painting them at the world-coordinates which match that index, what you would do is choose a tile from the map (like choosing a colour in MS Paint), and then wherever you click (or drag), figure out which array point that relates to, and set that index to be equal to that tile.
pros:
The sky is the limit; you can make whatever you want, make it fit any file-format you want to use, and make it handle any crazy stuff you want to throw at it...
cons:
...this of course, means you have to make it, yourself, and define the file-format you want to use, and write the logic to handle all of those zany ideas...
basic implementation
While I'd normally try to make this tidy, and JS-paradigm friendly, that would result in a LOT of code, here.
So I'll try to denote where it should probably be broken up into separate modules.
// assuming images are already loaded properly
// and have fired onload events, which you've listened for
// so that there are no surprises, when your engine tries to
// paint something that isn't there, yet
// this should all be wrapped in a module that deals with
// loading tile-maps, selecting the tile to "paint" with,
// and generating the data-format for the tile, for you to put into the array
// (or accepting plug-in data-formatters, to do so)
var selected_tile = null,
selected_tile_map = get_tile_map(), // this would be an image with your tiles
tile_width = 64, // in image-pixels, not canvas/screen-pixels
tile_height = 64, // in image-pixels, not canvas/screen-pixels
num_tiles_x = selected_tile_map.width / tile_width,
num_tiles_y = selected_tile_map.height / tile_height,
select_tile_num_from_map = function (map_px_X, map_px_Y) {
// there are *lots* of ways to do this, but keeping it simple
var tile_y = Math.floor(map_px_Y / tile_height), // 4 = floor(280/64)
tile_x = Math.floor(map_px_X / tile_width ),
tile_num = tile_y * num_tiles_x + tile_x;
// 23 = 4 down * 5 per row + 3 over
return tile_num;
};
// won't go into event-handling and coordinate-normalization
selected_tile_map.onclick = function (evt) {
// these are the coordinates of the click,
//as they relate to the actual image at full scale
map_x, map_y;
selected_tile = select_tile_num_from_map(map_x, map_y);
};
Now you have a simple system for figuring out which tile was clicked.
Again, there are lots of ways of building this, and you can make it more OO,
and make a proper "tile" data-structure, that you expect to read and use throughout your engine.
Right now, I'm just returning the zero-based number of the tile, reading left to right, top to bottom.
If there are 5 tiles per row, and someone picks the first tile of the second row, that's tile #5.
Then, for "painting", you just need to listen to a canvas click, figure out what the X and Y were,
figure out where in the world that is, and what array spot that's equal to.
From there, you just dump in the value of selected_tile, and that's about it.
// this might be one long array, like I did with the tile-map and the number of the tile
// or it might be an array of arrays: each inner-array would be a "row",
// and the outer array would keep track of how many rows down you are,
// from the top of the world
var world_map = [],
selected_coordinate = 0,
world_tile_width = 64, // these might be in *canvas* pixels, or "world" pixels
world_tile_height = 64, // this is so you can scale the size of tiles,
// or zoom in and out of the map, etc
world_width = 320,
world_height = 320,
num_world_tiles_x = world_width / world_tile_width,
num_world_tiles_y = world_height / world_tile_height,
get_map_coordinates_from_click = function (world_x, world_y) {
var coord_x = Math.floor(world_px_x / num_world_tiles_x),
coord_y = Math.floor(world_px_y / num_world_tiles_y),
array_coord = coord_y * num_world_tiles_x + coord_x;
return array_coord;
},
set_map_tile = function (index, tile) {
world_map[index] = tile;
};
canvas.onclick = function (evt) {
// convert screen x/y to canvas, and canvas to world
world_px_x, world_px_y;
selected_coordinate = get_map_coordinates_from_click(world_px_x, world_px_y);
set_map_tile(selected_coordinate, selected_tile);
};
As you can see, the procedure for doing one is pretty much the same as the procedure for doing the other (because it is -- given an x and y in one coordinate-set, convert it to another scale/set).
The procedure for drawing the tiles, then, is nearly the exact opposite.
Given the world-index and tile-number, work in reverse to find the world-x/y and tilemap-x/y.
You can see that part in your example code, as well.
This tile-painting is the traditional way of making 2d maps, whether we're talking about StarCraft, Zelda, or Mario Bros.
Not all of them had the luxury of having a "paint with tiles" editor (some were by hand in text-files, or even spreadsheets, to get the spacing right), but if you load up StarCraft or even WarCraft III (which is 3D), and go into their editors, a tile-painter is exactly what you get, and is exactly how Blizzard made those maps.
additions
With the basic premise out of the way, you now have other "maps" which are also required:
you'd need a collision-map to know which of those tiles you could/couldn't walk on, an entity-map, to show where there are doors, or power-ups or minerals, or enemy-spawns, or event-triggers for cutscenes...
Not all of these need to operate in the same coordinate-space as the world map, but it might help.
Also, you might want a more intelligent "world".
The ability to use multiple tile-maps in one level, for instance...
And a drop-down in a tile-editor to swap tile-maps.
...a way to save out both tile-information (not just X/Y, but also other info about a tile), and to save out the finished "map" array, filled with tiles.
Even just copying JSON, and pasting it into its own file...
Procedural Generation
The other way of doing this, the way you suggested earlier ("knowing how to connect rocks, grass, etc") is called Procedural Generation.
This is a LOT harder and a LOT more involved.
Games like Diablo use this, so that you're in a different randomly-generated environment, every time you play. Warframe is an FPS which uses procedural generation to do the same thing.
premise:
Basically, you start with tiles, and instead of just a tile being an image, a tile has to be an object that has an image and a position, but ALSO has a list of things that are likely to be around it.
When you put down a patch of grass, that grass will then have a likelihood of generating more grass beside it.
The grass might say that there's a 10% chance of water, a 20% chance of rocks, a 30% chance of dirt, and a 40% chance of more grass, in any of the four directions around it.
Of course, it's really not that simple (or it could be, if you're wrong).
While that's the idea, the tricky part of procedural generation is actually in making sure everything works without breaking.
constraints
You couldn't, for example have the cliff wall, in that example, appear on the inside of the high-ground. It can only appear where there's high ground above and to the right, and low-ground below and to the left (and the StarCraft editor did this automatically, as you painted). Ramps can only connect tiles that make sense. You can't wall off doors, or wrap the world in a river/lake that prevents you from moving (or worse, prevents you from finishing a level).
pros
Really great for longevity, if you can get all of your pathfinding and constraints to work -- not only for pseudo-randomly generating the terrain and layout, but also enemy-placement, loot-placement, et cetera.
People are still playing Diablo II, nearly 14 years later.
cons
Really difficult to get right, when you're a one-man team (who doesn't happen to be a mathematician/data-scientist in their spare time).
Really bad for guaranteeing that maps are fun/balanced/competitive...
StarCraft could never have used 100% random-generation for fair gameplay.
Procedural-generation can be used as a "seed".
You can hit the "randomize" button, see what you get, and then tweak and fix from there, but there'll be so much fixing for "balance", or so many game-rules written to constrain the propagation, that you'll end up spending more time fixing the generator than just painting a map, yourself.
There are some tutorials out there, and learning genetic-algorithms, pathfinding, et cetera, are all great skills to have... ...buuuut, for purposes of learning to make 2D top-down tile-games, are way-overkill, and rather, are something to look into after you get a game/engine or two under your belt.

Changing an Object's attributes also Affects Another Object

Problem: When I change the points of a polygon poly2, it also changes the points of another polygon poly!!
Why does changing one also change the other, and how do we decouple them?
console.log(poly.getPoints()[1].x); // 100
// Make a change to `poly2`
poly2.setPoints(poly.getPoints());
poly2.getPoints()[1].x=200
console.log(poly.getPoints()[1].x); // 200 (both poly and poly2 are affected!)
jsfiddle: http://jsfiddle.net/8hFyv/
poly2.setPoints(poly.getPoints());
This is your problem. The points array is the very same object.
Since you have arrays in your array, the slice(0) trick won't work, you need deep copy.
Fortunately, you're using jQuery, which has a method to do it.
Replace the above line with:
poly2.setPoints($.extend(true, [], poly.getPoints()));
Your poly and poly2 object are referencing the same array of points when you do this:
poly2.setPoints(poly.getPoints());
Change it to this:
poly2.setPoints([0, 0, 100, 0, 100, 100, 0, 100]);
To clone the points, vs. sharing them between polygons, you'll need to create new objects for each yourself.
You can do this with map:
poly2.setPoints(poly.getPoints().map(function (p) {
return { x: p.x, y: p.y };
}));
Or, with jQuery.map:
poly2.setPoints($.map(poly.getPoints(), function (p) {
return { x: p.x, y: p.y };
}));
The other answers are correct in assessing the problem, but there's another way you can solve it: "clone" the points array when you set it. In other words:
poly2.setPoints(poly.getPoints().slice());
If for some reason getPoints() returns something other than an array, you'll need a different cloning approach (eg. the one that axel.michel suggested), but since I think it does that should work for you.
The problem is, that poly.getPoints is a set of Kinetic Pointer Objects, to get rid of it, try the following:
poly2.setPoints(JSON.parse(JSON.stringify(poly.getPoints())));

Categories