Having problems emmiting 2 arrays to the client.
I have 2 objects and all instances are created on Server side . The socket.emit is from what I understood done on the Server 25s/s and per socket that exist.
Now i created bullets in each ship and want to get these to the client.
The thing that makes me headache is I am waiting on the Client side with socket.on per ship which is a 1:1 per each emitted socket from the server.
And now come a 1:n per each socket on the Server that is shooting the bullets. Can I actually emit 2 array with 2 emit execution or does it Need to be ohne emit from each socket with all data in one array?
My Problem is that the array bullet doesnt exist on the Client side !
So my ship values are at the client side anyhow I dont have a bullet array on the Client side
I tested it with a draw in a fixed x and y
for (var i in bullet){
ctx.fillText("X",100,100);
}
but the client draws nothing means I have no array at the client side. Also did a alert if I would have more then 10 bullets but even that one doesn’t pop up
Appreciate any help
App.js
for(var q in SOCKET_LIST){
var socket = SOCKET_LIST[q];
var f =0;
for(var k = 0;k<allbullets.length;k++)
{
if (allbullets[k].user_id === q)
{
packbul= {
x:allbullets[k].xtype.x,
y:allbullets[k].xtype.y,
userid:allbullets[k].user_id,
}// array end
}
f++;
} //end for
socket.broadcast.emit('newBullet',packbul);
if (allobjects[q] === undefined)
{
}
else{
console.log("q:"+q);
pack[q] = {
x:allobjects[q].xtype.x,
y:allobjects[q].xtype.y,
userid:q,
}// array end
socket.broadcast.emit('newClientship',pack[q]);
} // else end
} // For ebf.
},1000/25); // Set Interval End
Client
var ship = Array();
var bullet = Array();
socket.on('newClientship',function(data){
ship[data.userid]= data;
});
socket.on('newBullet',function(data){
bullet= data;
});
var previous; // var for renderworld
renderWorld = function(timestamp){
//setInterval(function(){
if (!previous) previous = timestamp;
elapsed = timestamp - previous;
updateFps();
ctx.font="150px Arial";
ctx.fillStyle = "white";
ctx.clearRect(0,0,canvas.width,canvas.height);
for ( var i in ship){ ctx.fillText(ship[i].userid,ship[i].x,ship[i].y);
}
if (bullet.length > 10)
{
alert("ted");
}
for (var i in bullet){
ctx.fillText("X",100,100);
}
drawFps(200,20) ;
previous = timestamp;
window.requestAnimationFrame(renderWorld);
//},1000/25);
}
Just found out that the for loop on Server side doesn’t work
It gives me a „undefined“ if I console.log (allbullets.length)
If I do a console.log(allbullets) it shows me that it exists .
Bulletobj {
user_id: 47,
type: 'Bullet',
radius: 2,
basespeed: 1,
speed: 1,
velX: 0.2979165941720021,
velY: 0.9545919038609927,
targetX: 863,
targetY: 2429,
xtype: Obj { x: 153, y: 154, radius: 3, selected: 0 },
angleDeg: 1.268286927738952,
realAngle: 72.66748817105554 }
Now found a formula to count the items of an obj
console.log("length:"+Object.keys(allbullets).length);
But even that only counts the items in one of the objects and always shows 12
I wanted to have the count of all bullets that have an instance
FYI My ship has a procedure that creates a new bullet
ClientObj.prototype.fire =function (x,y){
allbullets = new Bulletobj(this.xtype.x,this.xtype.y,x,y,1,1, this.user_id);
}
// had put this var outside outside functions for global
allbullets = Array();
App.js
var allbullets = [];
Bulletobj = function(x,y,targetX,targetY,shipid,speed,user_id){
this.user_id = user_id;
this.type = 'Bullet';
this.radius = 2;
this.basespeed = speed;
this.speed = 4;
this.velX = 0;
this.velY = 0;
this.targetX = targetX;
this.targetY = targetY;
this.xtype = new Obj(x,y,3);
w.objects.push(this);
Bulletobj.prototype.update =function (){
tx = this.targetX - this.xtype.x;
ty = this.targetY - this.xtype.y;
dist = Math.sqrt(tx * tx + ty * ty);
this.angleDeg = Math.atan2(ty,tx) ;
this.realAngle = (Math.atan2(ty,tx) * 180/Math.PI + 360 ) % 360 ;
this.velX = (tx / dist) * this.speed ;
this.velY = (ty / dist) * this.speed ;
if (parseInt(dist) > parseInt(this.radius / 2)) {
this.xtype.x += parseInt(this.velX);
this.xtype.y += parseInt(this.velY);
} // if distance schleife end
} // Bulletobj update end
} // Bulletobj end
setInterval(function(){
var pack = Array();
var packbul = Array();
var packbularray = Array();
var spliceArray = Array();
objcnt = w.objects.length;
var i=0;
while (i < objcnt)
{
w.objects[i].update();
if(w.objects[i].hitpoints <= 0 )
spliceArray.push(i);
i++;
} // endwhile
for(var k = 0;k<spliceArray.length;k++)
{
w.objects.splice(spliceArray[k],1);
}
for(var q in SOCKET_LIST){
var socket = SOCKET_LIST[q];
if (allbullets === undefined)
{
}
else {
for(var k = 0;k<allbullets.length;k++)
{
if(allbullets[k].user_id == q)
{
packbul= {
x:allbullets[k].xtype.x,
y:allbullets[k].xtype.y,
userid:allbullets[k].user_id,
}// array end
} // if end
//obj end
} // end else undefined objects
} //end for
console.log(packbul);
socket.emit('newBullet',packbul);
if (allobjects[q] === undefined)
{
}
else{
console.log("q:"+q);
pack[q] = {
x:allobjects[q].xtype.x,
y:allobjects[q].xtype.y,
userid:q,
}// array end
socket.broadcast.emit('newClientship',pack[q]);
} // else end
} // For socket
},1000/25); // Set Interval End
Index.html
var ship = Array();
var bullet= Array();
var bulletdata =Array();
socket.on('newClientship',function(data){
ship[data.userid]= data;
});
socket.on('newBullet',function(data){
;
bulletdata.push(data);
});
var previous; // var for renderworld
renderWorld = function(timestamp){
if (!previous) previous = timestamp;
elapsed = timestamp - previous;
updateFps();
ctx.font="150px Arial";
ctx.fillStyle = "white";
ctx.clearRect(0,0,canvas.width,canvas.height);
for ( var i in ship){ ctx.fillText(ship[i].userid,ship[i].x,ship[i].y);
}
for (var i in bulletdata){
ctx.fillText(".",bulletdata[i].x,bulletdata[i].y);
}
bulletdata = [];
drawFps(200,20) ;
previous = timestamp;
window.requestAnimationFrame(renderWorld);
}
So I managed to get one bullet over to the client. Problem now is that each time a player shoots a bullet the old bullet vanish and the new start from beginning same with another client . 1 st client shoots . If the second client starts shooting the bullet from player 1 is deleted. Normally I wanted to have each client with his own bullets and all bullets that are created as objects on Server side to be drawn on the clients.
The ships work perfect but somehow the bullet flickers and doesn’t fit into the canvas 😔
Thank you
Solved !!! it after 3 frustrating days omg
again thx for the push without it I would have been lost
The problem was mixing object array and standard array.
E.g var x = {} —> Object Array
var x = []. —> Standard Array
People keep saying that you shouldn’t emit objects but atm I am glad the bullets arrived
at client . Also with standard arrays I would need to split after a choosen index and have to set the dataset points by myself . With object arrays it is easier to access them.
Maybe somebody can tell me what I emit when I push an object array into a standard array at the Server side . Is this an object or standard array ? Are the object headers that create performance issues (this is what in read somewhere) included in that array I emit to the client?
I mean on the client I side I receive an array but in the for loop with the ctx I have an object array again which was inside the standard array
if(allbullets[k].user_id == q)
{
packbul= {
x:allbullets[k].xtype.x,
y:allbullets[k].xtype.y,
userid:allbullets[k].user_id,
}// array end
packbularray.push(packbul);
} // if end
Index.html
var ship = Array();
var bullet= Array();
var bulletdata =Array();
socket.on('newClientship',function(data){
ship[data.userid]= data;
//ship.x = data.x;
//ship.y = data.y;
//ship.user = data.userid;
});
socket.on('newBullet',function(data){
bullet= data;
//bulletdata.push(bullet);
//ship.x = data.x;
//ship.y = data.y;
//ship.user = data.userid;
});
Related
I'm trying to optimize the layer order of paths in Illustrator so that when sent to a laser cutter, the end of one path is close to the start of the next path reducing the travel time of the laser between each cut.
I've come up with the following code, which works, but could be further optimized considering length of lines, or through an annealing process. I'm posting it here in case anyone else is Googling 'Laser cutting optimization' and doesn't want to write their own code. Also if anyone can suggest improvements to the below code, I'd love to hear them.
// For this script to work, all paths to be optimised need to be on layer 0.
// Create a new empty layer in position 1 in the layer heirarchy.
// Run the script, all paths will move from layer 0 to layer 1 in an optimized order.
// Further optimisation possible with 'Annealing', but this will be a good first run optimization.
// Load into Visual Studio Code, follow steps on this website
// https://medium.com/#jtnimoy/illustrator-scripting-in-visual-studio-code-cdcf4b97365d
// to get setup, then run code when linked to Illustrator.
function test() {
if (!app.documents.length) {
alert("You must have a document open.");
return;
}
var docRef = app.activeDocument;
function endToStartDistance(endPath, startPath) {
var endPoint = endPath.pathPoints[endPath.pathPoints.length - 1].anchor;
var startPoint = startPath.pathPoints[0].anchor;
var dx = (endPoint[0] - startPoint[0]);
var dy = (endPoint[1] - startPoint[1]);
var dist = Math.pow((Math.pow(dx, 2) + Math.pow(dy, 2)), 0.5);
return dist;
}
function Optimize(items) {
var lastPath, closest, minDist, delIndex, curItem, tempItems = [];
var topLayer = app.activeDocument.layers[0];
var newLayer = app.activeDocument.layers[1];
for (var x = 1, len = items.length; x < len; x++) {
tempItems.push(items[x]);
}
lastPath = items[0];
lastPath.move(newLayer, ElementPlacement.PLACEATBEGINNING);
while (tempItems.length) {
closest = tempItems[0];
minDist = endToStartDistance(lastPath, closest);
delIndex = 0;
for (var y = 1, len = tempItems.length; y < len; y++) {
curItem = tempItems[y];
if (endToStartDistance(lastPath, curItem) < minDist) {
closest = curItem;
minDist = endToStartDistance(lastPath, closest);
delIndex = y;
}
}
$.writeln(minDist);
//closest.zOrder(ZOrderMethod.BRINGTOFRONT);
closest.move(newLayer, ElementPlacement.PLACEATBEGINNING);
lastPath = closest;
tempItems.splice(delIndex, 1);
}
}
var allPaths = [];
for (var i = 0; i < documents[0].pathItems.length; i++) {
allPaths.push(documents[0].pathItems[i]);
//$.writeln(documents[0].pathItems[i].pathPoints[0].anchor[0])
}
Optimize(allPaths);
}
test();
Version 2 of the above code, some changes include the ability to reverse paths if this results in a reduced distance for the cutting head to move between paths, and added comments to make the code easier to read.
// Create a new empty layer in position 1 in the layer heirarchy.
// Run the script, all paths will move from their current layer to layer 1 in an optimized order.
// Further optimisation possible with 'Annealing', but this will be a good first run optimization.
// Load into Visual Studio Code, follow steps on this website
// https://medium.com/#jtnimoy/illustrator-scripting-in-visual-studio-code-cdcf4b97365d
// to get setup, then run code when linked to Illustrator.aa
function main() {
if (!app.documents.length) {
alert("You must have a document open.");
return;
}
var docRef = app.activeDocument;
// The below function gets the distance between the end of the endPath vector object
// and the start of the startPath vector object.
function endToStartDistance(endPath, startPath) {
var endPoint = endPath.pathPoints[endPath.pathPoints.length - 1].anchor;
var startPoint = startPath.pathPoints[0].anchor;
var dx = (endPoint[0] - startPoint[0]);
var dy = (endPoint[1] - startPoint[1]);
var dist = Math.pow((Math.pow(dx, 2) + Math.pow(dy, 2)), 0.5);
return dist;
}
// The below function gets the distance between the end of the endPath vector object
// and the end of the startPath vector object.
function endToEndDistance(endPath, startPath) {
var endPoint = endPath.pathPoints[endPath.pathPoints.length - 1].anchor;
var startPoint = startPath.pathPoints[startPath.pathPoints.length - 1].anchor;
var dx = (endPoint[0] - startPoint[0]);
var dy = (endPoint[1] - startPoint[1]);
var dist = Math.pow((Math.pow(dx, 2) + Math.pow(dy, 2)), 0.5);
return dist;
}
// The below function iterates over the supplied list of tempItems (path objects) and checks the distance between
// the end of path objects and the start/end of all other path objects, ordering the objects in the layer heirarchy
// so that there is the shortest distance between the end of one path and the start of the next.
// The function can reverse the direciton of a path if this results in a smaller distance to the next object.
function Optimize(tempItems) {
var lastPath, closest, minDist, delIndex, curItem;
var newLayer = app.activeDocument.layers[1]; // There needs to be an empty layer in position 2 in the layer heirarchy
// This is where the path objects are moved as they are sorted.
lastPath = tempItems[0]; // Arbitrarily take the first item in the list of supplied items
tempItems.splice(0, 1); // Remove the first item from the list of items to be iterated over
lastPath.move(newLayer, ElementPlacement.PLACEATBEGINNING); // Move the first item to the first position in the new layer
while (tempItems.length) { // Loop over all supplied items while the length of this array is not 0.
// Items are removed from the list once sorted.
closest = tempItems[0]; // Start by checking the distance to the first item in the list
minDist = Math.min(endToStartDistance(lastPath, closest), endToEndDistance(lastPath, closest));
// Find the smallest of the distances between the end of the previous path item
// and the start / end of this next item.
delIndex = 0; // The delIndex is the index to be removed from the tempItems list after iterating through
// the entire list.
for (var y = 1, len = tempItems.length; y < len; y++) {
// Iterate over all items in the list, starting at item 1 (item 0 already being used above)
curItem = tempItems[y];
if (endToStartDistance(lastPath, curItem) < minDist || endToEndDistance(lastPath, curItem) < minDist) {
// If either the end / start distance to the current item is smaller than the previously
// measured minDistance, then the current path item becomes the new smallest entry
closest = curItem;
minDist = Math.min(endToStartDistance(lastPath, closest), endToEndDistance(lastPath, closest));
// The new minDistace is set
delIndex = y; // And the item is marked for removal from the list at the end of the loop.
}
}
if (endToEndDistance(lastPath, closest) < endToStartDistance(lastPath, closest)) {
reversePaths(closest); // If the smallest distance is yielded from the end of the previous path
// To the end of the next path, reverse the next path so that the
// end-to-start distance between paths is minimised.
}
closest.move(newLayer, ElementPlacement.PLACEATBEGINNING); // Move the closest path item to the beginning of the new layer
lastPath = closest; // The moved path item becomes the next item in the chain, and is stored as the previous item
// (lastPath) for when the loop iterates again.
tempItems.splice(delIndex, 1); // Remove the item identified as closest in the previous loop from the list of
// items to iterate over. When there are no items left in the list
// The loop ends.
}
}
function reversePaths(theItems) { // This code taken / adapted from https://gist.github.com/Grsmto/bfe1541957a0bb17972d
if (theItems.typename == "PathItem" && !theItems.locked && !theItems.parent.locked && !theItems.layer.locked) {
pathLen = theItems.pathPoints.length;
for (k = 0; k < pathLen / 2; k++) {
h = pathLen - k - 1;
HintenAnchor = theItems.pathPoints[h].anchor;
HintenLeft = theItems.pathPoints[h].leftDirection;
HintenType = theItems.pathPoints[h].pointType;
HintenRight = theItems.pathPoints[h].rightDirection;
theItems.pathPoints[h].anchor = theItems.pathPoints[k].anchor;
theItems.pathPoints[h].leftDirection = theItems.pathPoints[k].rightDirection;
theItems.pathPoints[h].pointType = theItems.pathPoints[k].pointType;
theItems.pathPoints[h].rightDirection = theItems.pathPoints[k].leftDirection;
theItems.pathPoints[k].anchor = HintenAnchor;
theItems.pathPoints[k].leftDirection = HintenRight;
theItems.pathPoints[k].pointType = HintenType;
theItems.pathPoints[k].rightDirection = HintenLeft;
}
}
}
var allPaths = []; // Grab every line in the document
for (var i = 0; i < documents[0].pathItems.length; i++) {
allPaths.push(documents[0].pathItems[i]);
// This could be better changed to the selected objects, or to filter only objects below a certain
// stroke weight so that raster paths are not affected, but cut paths are.
}
Optimize(allPaths); // Feed all paths in the document into the optimize function.
}
main(); // Call the main function, executing the above code.
So, for a final project I'm trying to make a game with three different meteors; Bronze, Silver and Gold. While the Bronze array works fine in Setup(), the Silver and Gold meteors go at high speeds for some unknown reason.
function setup() {
createCanvas(windowWidth, windowHeight);
spaceship = new Spaceship(100, 100, 5, spaceshipImage, bulletImage, 40);
healthStar = new Star(1000, 100, 10, healthStarImage, 50);
//the Meteor Array
// Run a for loop numMeteor times to generate each meteor and put it in the array
// with random values for each star
for (let i = 0; i < numMeteor; i++) {
let meteorX = random(0, width);
let meteorY = random(0, height);
let meteorSpeed = random(2, 20);
let meteorRadius = random(10, 60);
meteor.push(new Meteor(meteorX, meteorY, meteorSpeed, meteorBronzeImage, meteorRadius));
}
}
// draw()
//
// Handles input, movement, eating, and displaying for the system's objects
function draw() {
// Set the background to a safari scene
background(skyBackground);
// Check if the game is in play
if (playing == true) {
// Handle input for the tiger
spaceship.handleInput();
// Move all the "animals"
spaceship.move();
healthStar.move();
if (spaceship.dodges >= 5){
levelTwo = true;
}
//lvl 2
if (levelTwo == true){
meteor = [];
for (let i = 0; i < numMeteor; i++) {
let meteorX = random(0, width);
let meteorY = random(0, height);
let meteorSpeed = random(2, 20);
let meteorRadius = random(10, 60);
meteor.push(new Meteor(meteorX, meteorY, meteorSpeed, meteorSilverImage, meteorRadius));
}
}
if (spaceship.dodges >= 8){
levelThree = true;
}
//lvl 3
if (levelThree == true){
levelTwo = false;
meteor = [];
for (let i = 0; i < numMeteor; i++) {
let meteorX = random(0, width);
let meteorY = random(0, height);
let meteorSpeed = random(2, 20);
let meteorRadius = random(10, 60);
meteor.push(new Meteor(meteorX, meteorY, meteorSpeed, meteorGoldImage, meteorRadius));
}
}
// Handle the tiger and lion eating any of the star
spaceship.handleEating(healthStar);
//
spaceship.handleBullets();
// Handle the tragic death of the tiger
spaceship.handleDeath();
// Check to see when the game is over
checkGameOver();
// Display all the "animals"
spaceship.display();
healthStar.display();
// Display and making sure the tiger can eat the copies of the star
for (let i = 0; i < meteor.length; i++) {
meteor[i].move();
meteor[i].display();
//meteor[i].handleDamage();
spaceship.handleHurting(meteor[i]);
spaceship.handleDodging(meteor[i]);
}
}
// Once the game is over, display a Game Over Message
if (gameOver == true) {
displayGameOver();
}
// Otherwise we display the message to start the game
else {
displayStartMessage();
}
}
I've tried to change the speeds, made the levels false, nothing's working other than the Bronze meteors.
Your level 2 and level 3 meteor initialization is inside your draw loop. They include a meteor = [] statement. From what you've provided, that suggests your meteor array is getting cleared every single draw iteration. They never have a chance to move, you're getting fresh random meteors each time.
If the array clearing within your draw loop is in fact the issue, you'll need to add a way to track if the level initialization has been completed, so that it only occurs once. A simple flag, an idempotent function, something like that.
// Extract meteor generation to it's own function so you dont need to repeat it
function generateMeteors(meteorImage) {
let meteor = [];
for (let i = 0; i < numMeteor; i++) {
let meteorX = random(0, width);
let meteorY = random(0, height);
let meteorSpeed = random(2, 20);
let meteorRadius = random(10, 60);
meteor.push(new Meteor(meteorX, meteorY, meteorSpeed, meteorBronzeImage, meteorRadius));
}
return meteor;
}
function setup() {
createCanvas(windowWidth, windowHeight);
spaceship = new Spaceship(100, 100, 5, spaceshipImage, bulletImage, 40);
healthStar = new Star(1000, 100, 10, healthStarImage, 50);
//the Meteor Array
// meteor is apparently global, or otherwise in scope here
meteor = generateMeteors(meteorBronzeImage)
}
function draw() {
/**
Code removed for clarity
**/
if (spaceship.dodges >= 5) {
levelTwo = true;
}
//lvl 2
// levelTwoInitialized tracks if we've initialized the meteor array for this level
if (levelTwo == true && levelTwoInitialized == false){
meteor = generateMeteors(meteorSilverImage);
levelTwoInitialized = true;
}
if (spaceship.dodges >= 8){
levelThree = true;
}
//lvl 3
// levelThreeInitialized tracks if we've initialized the meteor array for this level
if (levelThree == true && levelThreeInitialized == false){
levelTwo = false;
meteor = generateMeteors(meteorGoldImage);
levelThreeInitialized = true;
}
//... rest of code
One possible way to do this is above. Level initialization flags like levelThreeInitialized would have to be declared somewhere, wherever you're tracking your game/program state.
If you want to retain the previous level's meteors, you could do something like:
if (levelTwo == true && levelTwoInitialized == false){
// add silver meteors to the existing bronze set
meteor = meteor.concat(generateMeteors(meteorSilverImage));
levelTwoInitialized = true;
}
//... rest of code
If you want to create a new set with multiple types:
if (levelTwo == true && levelTwoInitialized == false){
// make NEW silver and bronze meteors
meteor = generateMeteors(meteorSilverImage)
.concat(generateMeteors(meteorBronzeImage);
levelTwoInitialized = true;
}
//... rest of code
I have to create a animation for waves . I need to control the speed of the waves depends on the availability of the data. Is it possible to speed up the waves. I'm using canvas for the waves.
Thanks in advance
Fiddle:https://jsfiddle.net/Chaitanya_Kumar/6ztr0Lfh/
function animate() {
if (x > data.length - 1) {
return;
}
if (continueAnimation) {
requestAnimationFrame(animate);
}
if (x++ < panAtX) {
var temp = data[x];
var final = constant-(temp);
ctx.fillRect(x, final, 1, 1);
ctx.lineTo(x, final);
ctx.stroke();
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath(); // reset the path
for (var xx = 0; xx < panAtX; xx++) {
var y = data[x - panAtX + xx];
var final = constant - (y);
ctx.fillRect(xx, final, 1, 1);
ctx.lineTo(xx, final);
}
ctx.stroke();
}
}
Sub sampling data
Below is an example of data sampling. It uses linear interpolation to subsample a data source and display that data on a rolling graph display.
Regularly interval data.
The data from your question and fiddle suggest that you have a constant sample rate interval and that you want to vary the display rate of that data. This is what I have done in the demo below.
About the demo
The graph is a real-time display of the data and its speed from left to right is dependent on the rate at which you call the sample function.
displayBuffer.readFrom(dataSource, dataSpeed, samplesPerFrame)
displayBuffer is the object that holds the displayable data
dataSource is the source of data and has a read and seek function and a readPos You seek to a position dataSource.seek(0.01); move ahead 0.01 data samples and then read the data dataSource.read(); and the linear interpolated value is returned.
This allows you to speed up or slow down data streaming from the source data.
The data reader object
//------------------------------------------------------------------------------
// data reader reads from a data source
const dataReader = {
readPos : 0,
seek(amount){ // moves read pos forward or back
if(this.data.length === 0){
this.readPos = 0;
return 0;
}
this.readPos += amount;
this.readPos = this.readPos < 0 ? 0 :this.readPos >= this.data.length ? this.data.length - 1 : this.readPos;
return this.readPos;
},
// this function reads the data at read pos. It is a linear interpolation of the
// data and does nor repressent what the actual data may be at fractional read positions
read(){
var fraction = this.readPos % 1;
var whole = Math.floor(this.readPos);
var v1 = this.data[Math.min(this.data.length-1,whole)];
var v2 = this.data[Math.min(this.data.length-1,whole + 1)];
return (v2 - v1) * fraction + v1;
},
}
Timestamped data source.
The demo can be adapted by adding to the dataReader.
If your data sample rate is irregular than you will need to add a timestamp for each sample. You then add a timeSeek function that is similare to seek but uses the slope between time samples to calculate the read position for a given time. It will require sampling of each sample from the current sampled time to the next (in the seek direction) making CPU cycles needed to seek indeterminant.
The following is an example seekTime that finds the readPos (from above dataReader object) for time shifted forward by the timeShift argument. the object's readTime and readPos properties are updated and the next read() call will return the data at dataSource.readTime.
readTime : 0, // current seeked time
seekTime(timeShift){ // Example is forward seek only
if(this.timeStamps.length === 0){
this.readPos = 0;
return 0;
}
this.readTime += timeShift; // set new seeked time
var readPos = Math.floor(this.readPos);
// move read pos forward until at correct sample
while(this.timeStamps[readPos] > this.readTime &&
readPos++ < this.timeStamps.length);
// Warning you could be past end of buffer
// you will need to check and set seek time to the last
// timestamp value and exit. Code below the following line
// will crash if you dont vet here.
//if(readPos === this.timeStamps.length)
// now readPos points to the first timeStamp less than the needed
// time position. The next read position should be a time ahead of the
// needed time
var t1 = this.timeStamps[readPos]; // time befor seekTime
var t2 = this.timeStamps[readPos+1]; // time after seekTime
// warning divide by zero if data bad
var fraction = (this.readTime-t1)/(t2-t1)); // get the sub sample fractional location for required time.
this.readPos = readPos + fraction;
return this.readPos;
},
Warning I have omitted all safety checks. You should check for buffer end, bad time shift values. If time stamped data has bad samples you will get a divide by zero that will make the dataReader return only NaN from that point on and throw for any reads. So vet for safety.
Note For the above time stamped function to work you will need to ensure that for each data sample there is a corresponding timeStamp. If there is not a one to one matching time stamp of each sample the above code will not work.
Changes to the dataDisplay are simple. Just change the seek call in the function
dataDisplay.readFrom(dataSource,speed,samples) to dataSource.seekTime(speed / samples) the speed now represents time rather than samples. (or I just overwrite the seek() function with seekTime() if I have time stamps) this allows the dataDisplay object to handle both timeStamped and regular interval data as is.
Demo
The example samples random data and displays it at variable speed and sampling rates. Use left right to set display speed. The framerate is 60fps thereabouts but you can make the speed variable scaled to the time between frames.
var ctx = canvas.getContext("2d");
window.focus();
//==============================================================================
// the current data read speed
var dataSpeed = 1;
var samplesPerFrame = 1;
requestAnimationFrame(mainLoop); // start animation when code has been parsed and executed
//------------------------------------------------------------------------------
// data reader reads from a data source
const dataReader = {
readPos : 0,
seek(amount){ // moves read pos forward or back
if(this.data.length === 0){
this.readPos = 0;
return 0;
}
this.readPos += amount;
this.readPos = this.readPos < 0 ? 0 :this.readPos >= this.data.length ? this.data.length - 1 : this.readPos;
return this.readPos;
},
// this function reads the data at read pos. It is a linear interpolation of the
// data and does nor repressent what the actual data may be at fractional read positions
read(){
var fraction = this.readPos % 1;
var whole = Math.floor(this.readPos);
var v1 = this.data[Math.min(this.data.length-1,whole)];
var v2 = this.data[Math.min(this.data.length-1,whole + 1)];
return (v2 - v1) * fraction + v1;
},
}
//------------------------------------------------------------------------------
// Create a data source and add a dataReader to it
const dataSource = Object.assign({
data : [],
},dataReader
);
// fill the data source with random data
for(let i = 0; i < 100000; i++ ){
// because random data looks the same if sampled every 1000 or 1 unit I have added
// two waves to the data that will show up when sampling at high rates
var wave = Math.sin(i / 10000) * 0.5;
wave += Math.sin(i / 1000) * 0.5;
// high frequency data shift
var smallWave = Math.sin(i / 100) * (canvas.height / 5);
// get a gaussian distributed random value
dataSource.data[i] = Math.floor(smallWave + ((wave + Math.random()+Math.random()+Math.random()+Math.random()+Math.random()) / 5) * canvas.height);
}
//------------------------------------------------------------------------------
// Data displayer used to display a data source
const dataDisplay = {
writePos : 0,
width : 0,
color : "black",
lineWidth : 1,
// this function sets the display width which limits the data buffer
// when it is called all buffers are reset
setDisplayWidth(width){
this.data.length = 0;
this.width = width;
this.writePos = 0;
if(this.lastRead === undefined){
this.lastRead = {};
}
this.lastRead.mean = 0;
this.lastRead.max = 0;
this.lastRead.min = 0;
},
// this draws the buffered data scrolling from left to right
draw(){
var data = this.data; // to save my self from writing this a zillion times
const ch = canvas.height / 2;
if(data.length > 0){ // only if there is something to draw
ctx.beginPath();
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.color;
ctx.lineJoin = "round";
if(data.length < this.width){ // when buffer is first filling draw from start
ctx.moveTo(0, data[0])
for(var i = 1; i < data.length; i++){
ctx.lineTo(i, data[i])
}
}else{ // buffer is full and write position is chasing the tail end
ctx.moveTo(0, data[this.writePos])
for(var i = 1; i < data.length; i++){
ctx.lineTo(i, data[(this.writePos + i) % data.length]);
}
}
ctx.stroke();
}
},
// this reads data from a data source (that has dataReader functionality)
// Speed is in data units,
// samples is number of samples per buffer write.
// samples is only usefull if speed > 1 and lets you see the
// mean, min, and max of the data over the speed unit
// If speed < 1 and sample > 1 the data is just a linear interpolation
// so the lastRead statistics are meaningless (sort of)
readFrom(dataSource,speed,samples){ // samples must be a whole positive number
samples = Math.floor(samples);
var value = 0;
var dataRead;
var min;
var max;
for(var i = 0; i < samples; i ++){ // read samples
dataSource.seek(speed / samples); // seek to next sample
dataRead = dataSource.read(); // read the sample
if(i === 0){
min = dataRead;
max = dataRead;
}else{
min = Math.min(dataRead,min);
max = Math.min(dataRead,max);
}
value += dataRead;
}
// write the samples data and statistics.
this.lastRead.min = min;
this.lastRead.max = max;
this.lastRead.delta = value / samples - this.lastRead.mean;
this.lastRead.mean = value / samples;
this.data[this.writePos] = value / samples;
this.writePos += 1;
this.writePos %= this.width;
}
}
// display data buffer
var displayBuffer = Object.assign({ // this data is displayed at 1 pixel per frame
data : [], // but data is written into it at a variable speed
},
dataDisplay // add display functionality
);
//------------------------------------------------------------------------------
// for control
const keys = {
ArrowLeft : false,
ArrowRight : false,
ArrowUp : false,
ArrowDown : false,
}
function keyEvent(event){
if(keys[event.code] !== undefined){
event.preventDefault();
keys[event.code] = true;
}
}
addEventListener("keydown",keyEvent);
//------------------------------------------------------------------------------
function mainLoop(time){
ctx.clearRect(0,0,canvas.width,canvas.height);
if(canvas.width !== displayBuffer.width){
displayBuffer.setDisplayWidth(canvas.width);
}
displayBuffer.readFrom(dataSource,dataSpeed,samplesPerFrame);
displayBuffer.draw();
//-----------------------------------------------------------------------------
// rest is display UI and stuff like that
ctx.font = "16px verdana";
ctx.fillStyle = "black";
//var dataValue =displayBuffer.lastRead.mean.toFixed(2);
//var delta = displayBuffer.lastRead.delta.toFixed(4);
var readPos = dataSource.readPos.toFixed(4);
//if(displayBuffer.lastRead.delta > 0){ delta = "+" + delta }
// ctx.fillText("Data : " + dataValue + " ( " +delta +" )" ,4,18);
ctx.setTransform(0.9,0,0,0.89,4,18);
ctx.fillText("Speed : " + dataSpeed.toFixed(3) + ", Sample rate :" +samplesPerFrame + ", Read # "+readPos ,0,0);
ctx.setTransform(0.7,0,0,0.7,4,32);
if(samplesPerFrame === 1){
ctx.fillText("Keyboard speed -left, +right Sample rate +up",0,0);
}else{
ctx.fillText("Keyboard speed -left, +right Sample rate -down, +up",0,0);
}
ctx.setTransform(1,0,0,1,0,0);
if(keys.ArrowLeft){
keys.ArrowLeft = false;
if(dataSpeed > 1){
dataSpeed -= 1;
}else{
dataSpeed *= 1/1.2;
}
}
if(keys.ArrowRight){
keys.ArrowRight = false;
if(dataSpeed >= 1){
dataSpeed += 1;
}else{
dataSpeed *= 1.2;
if(dataSpeed > 1){ dataSpeed = 1 }
}
}
if(keys.ArrowUp){
keys.ArrowUp = false;
samplesPerFrame += 1;
}
if(keys.ArrowDown){
keys.ArrowDown = false;
samplesPerFrame -= 1;
samplesPerFrame = samplesPerFrame < 1 ? 1 : samplesPerFrame;
}
requestAnimationFrame(mainLoop);
}
canvas {
border : 2px black solid;
}
<canvas id=canvas width=512 height=200></canvas>
Reading and displaying data this way is quick and simple. It is easy it add grid markings and data processing to the data source and display data. The demo (regular interval data) can easily handle displaying large data sources while zooming in and out on data. Note that for timeStamped data the above seekTime function is not suitable for large datasets. You will need to index such data for more effective seek times.
I'm writing an A* pathing script for a game set on a 7x7 grid of tiles with the player always in the middle (tile 24). Zeros are added as a visual and it's actually one array, not a 7x7 2D array.
[00,01,02,03,04,05,06]
[07,08,09,10,11,12,13]
[14,15,16,17,18,19,20]
[21,22,23,24,25,26,27]
[28,29,30,31,32,33,34]
[35,36,37,38,39,40,41]
[42,43,44,45,46,47,48]
The game is server-driven so the player uses relative coordinates. What that means is, if the player moves, tile[0] changes. The short version of that is the player will always move from tile 24, which is the center tile. The grid is hard coded in, but if I post it publicly I'll change the code a little; no problem.
The function should take a destination and find a good path from tile 24 to that square but what it actually does it return "undefined".
If I input 24 I want the game to output an array like this
[18,12,6]
Here's the code:
z = 0;
function pathTo(goal){
var createPath = function (goal){
var createNode = function(i){
this.id = i;
this.g = Infinity;
this.f = Infinity;
this.parent = null;
this.open = null;
};
this.nodes = Array(49);
for(i=0;i<this.nodes.length;i++){
this.nodes[i] = new createNode(i);
}
this.start = this.nodes[24];
this.start.g = 0;
this.currentNodeId = 24;
this.goal = this.nodes[goal];
this.bestPath = null;
};//end createPath
var getBestNeighbor = function(nodeId){
z++
if(z>50){throw z}debugger;
console.log(nodeId);
var getG = function(parentG){
//here you can check the map for water, sand, and ruins penalties
/*
default = 1
path = .9
water = 3
*/
return (parentG + 1);
};
var closeNode = function (node){
node.open = false;
};//end closeNode
var getF = function(startId,endId,g){
if(g>9){
throw g;
}
var startX = startId % 7;
var startY = (startId - startX) / 7;
var endX = endId % 7;
var endY = (endId - endX) / 7;
var h = Math.sqrt( Math.pow((startX - endX) , 2 ) + Math.pow(( startY - endY ), 2 ) );
console.log("Start.id:"+startId+"H:"+h+" Start.id.g:"+g);
return (h + g);
};//end getF
var tracePath = function(tracedNode){
path.bestPath = [];
while(tracedNode != path.start){
path.bestPath.unshift(tracedNode.id);
tracedNode = tracedNode.parent;
}
return path.bestPath;
};//end tracePath
var getNeighborNodeId = function(x,y,currentId){return currentId + (y*7) + x;};//end getNeighborNodeId
if(path.bestPath === null){
var neighborNode = {};
var bestNode = {f: Infinity};
if(nodeId == path.goal.id){//may need to pass path
return tracePath(path.nodes[nodeId]);
}else{
for(x=-1;x<=1;x++){
for(y=-1;y<=1;y++){
var nnId = getNeighborNodeId(x,y,nodeId);
if(nnId==24){debugger}
if( ( (x!=0) && (y!=0) ) ||( (nnId>=0) && (nnId<=48))){
var neighborNode = path.nodes[nnId];
if(neighborNode.open === null){ neighborNode.open = true; }
if(neighborNode.open === true ){//don't check closed neighbors
if(typeof neighborNode === "object"){
neighborNode.parent = path.nodes[nodeId]
debugger;
neighborNode.g = getG(neighborNode.parent.g);
neighborNode.f = getF(neighborNode.id , path.goal.id , neighborNode.g);
if( neighborNode.f < bestNode.f){
bestNode = neighborNode;
}//endif
}//endif
}//endif Note: if the node isn't null or true, it's false.
}
}//endfor
}//endfor - Note: Here we should have the best neighbor
if(bestNode.f == Infinity){
closeNode(path.nodes[nodeId]);//need escape for no possible path
return;
}else{
//bestNode.parent = path.nodes[nodeId];
path.currentNodeId = bestNode.id;
getBestNeighbor(bestNode.id);
}//endelse
}//endelse
}//endif
};//end getBestNeighbor
var path = new createPath(goal);
while(path.bestPath === null){
getBestNeighbor(path.currentNodeId);
}//end while
return path.bestPath;
}//end pathTo
console.log(pathTo(41)); //testing with 6
and a JSFiddle link: https://jsfiddle.net/jb4xtf3h/
It's my first time not just slapping globals everywhere, so it may have a scope issue I'm not familiar with.
Most likely my issue is in the getNeighborId function; I don't think I have anything declaring a good node's parent.
The problem is that it goes NW three times instead of NE three times. That probably means I have a mistake in the getBestNeighbor function where I'm reading a -1 as a 1.
Also I don't think I'm escaping the recursive function correctly.
For some reason, when I put in 41 it gets really confused. This either has to do with how I set G and H which are classically used in A* to record distance traveled on this path and the estimated distance remaining. Specifically the G number is wrong because it's taking bad steps for some reason.
Here is the working code. I didn't implement walls or anything but I do show where you would do that. All you need to do is close all the nodes that are walls before you begin pathing and you can assign movement penalties if you want the AI to "know" to avoid water or sand.
I actually can't pin down a single problem but a major one was the way the statement:
if( ( (x!=0) && (y!=0) ) ||( (nnId>=0) && (nnId<=48))){
was changed to:
if( ( !(x==0 && y==0) && ( nnId>=0 && nnId<=48))){
The intent of this line was to prevent searching the tile you are standing on x,y = (0,0) and also to make sure that the neighbor you want to look at is on the grid (7x7 grid has 49 squares numbered 0-48)
What I was trying to say is "IF X & Y ARE BOTH NOT ZERO" apparently that actually makes it the same as an or statement so if either square was 0 it skipped it and tiles that needed that space were having problems since there were several directions that weren't working.
I hope that helps somebody if they need a nice simple pathing script I tried really hard to make the code readable and I'm not the strongest coder in the world but a working A* script in 100 lines that I think is fairly easy to follow. If you are reading this and you're not familiar with A* pathing what you might need to know is
H is your heuristic value it's an estimation of the remaining distance form a tile. In this code it's under the path object path.nodes[array#].h
G is the distance you've moved so far to get to that square path.nodes[array#].g.
F just adds h+g for the total value. This pseudocode on Wikipedia helped me write it.
var z = 0;
function pathTo(goal){
var createPath = function (goal){
var createNode = function(i){
this.id = i;
this.g = Infinity;
this.f = Infinity;
this.parent = null;
this.open = null;
};
this.nodes = Array(49);
for(i=0;i<this.nodes.length;i++){
this.nodes[i] = new createNode(i);
}
this.start = this.nodes[24];
this.start.g = 0;
this.currentNodeId = 24;
this.goal = this.nodes[goal];
this.bestPath = null;
};//end createPath
var path = new createPath(goal);
var getBestNeighbor = function(nodeId){
var getG = function(parentG){
//here you can check the map for water, sand, and ruins penalties
/*
default = 1
path = .9
water = 3
*/
return (parentG + 1);
};
var closeNode = function (node){
node.open = false;
};//end closeNode
var getF = function(startId,endId,g){
var startX = startId % 7;
var startY = (startId - startX) / 7;
var endX = endId % 7;
var endY = (endId - endX) / 7;
var h = Math.sqrt( Math.pow((startX - endX) , 2 ) + Math.pow(( startY - endY ), 2 ) );
return (h + g);
};//end getF
var tracePath = function(tracedNode){
path.bestPath = [];
while(tracedNode != path.start){
path.bestPath.unshift(tracedNode.id);
tracedNode = tracedNode.parent;
}
return path.bestPath;
};//end tracePath
var getNeighborNodeId = function(x,y,currentId){return currentId + (y*7) + x;};//end getNeighborNodeId
debugger;
z++
if(z>50){throw z}
if(path.bestPath === null){
var neighborNode = {};
var bestNode = {f: Infinity};
if(nodeId == path.goal.id){//may need to pass path
return tracePath(path.nodes[nodeId]);
}else{
for(y=-1;y<=1;y++){
for(x=-1;x<=1;x++){
var nnId = getNeighborNodeId(x,y,nodeId);
if( ( !(x==0 && y==0) && ( nnId>=0 && nnId<=48))){
var neighborNode = path.nodes[nnId];
if(path.nodes[nodeId].parent!=neighborNode){
if(neighborNode.open === null){ neighborNode.open = true; }
if(neighborNode.open === true ){//don't check closed neighbors
if(typeof neighborNode === "object"){
neighborNode.parent = path.nodes[nodeId]
neighborNode.g = getG(neighborNode.parent.g);
neighborNode.f = getF(neighborNode.id , path.goal.id , neighborNode.g);
if( neighborNode.f < bestNode.f){
bestNode = neighborNode;
}//endif
}//endif
}
}//endif Note: if the node isn't null or true, it's false.
}
}//endfor
}//endfor - Note: Here we should have the best neighbor
if(bestNode.f >= 50){
closeNode(path.nodes[nodeId]);//need escape for no possible path
return;
}else{
path.currentNodeId = bestNode.id;
getBestNeighbor(bestNode.id);
}//endelse
}//endelse
}//endif
};//end getBestNeighbor
while(path.bestPath === null){
getBestNeighbor(path.currentNodeId);
}//end while
return path.bestPath;
}//end pathTo
myPath = pathTo(41); //testing with 6
console.log("path2:"+myPath);
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;
};