I am drawing points on a canvas with javascript, like so:
function addPointToMap(point) {
var pointRadius = (document.getElementById(point.canvasId).height * (2 / 66)) / 2;
var context = document.getElementById(point.canvasId).getContext("2d");
context.beginPath();
context.arc(point.x, point.y, pointRadius, 0, 2 * Math.PI);
context.fillStyle = "red";
context.fill();
}
and all the points I am drawing are stored in an array, pointMap. I want the user to only be able to draw one point if a checkbox is ticked, and draw many points if it is not ticked. A new point should override an old one. In order to do this, I have decided to add the new point to the array, and then remove the old one and refresh the canvas. The problem is that pointMap = pointMap.pop(); is returning an empty array. How do I get the most recent entry in an array and delete all the other entries? Here is what I have so far:
if (questionId == 41) {
if (pointMap.length == 1) {
//do nothing, user only has 1 point
} else {
console.log("PointMap: " + pointMap); //ex. returns [Point, Point] (Point is a custom class I wrote to store the point x and y values)
pointMap = pointMap.pop(); //this line does not work
console.log("PointMap: " + pointMap); //ex. returns []
refreshCanvas();
}
}
Where am I going wrong? can anyone steer me in the right direction?
pop returns the popped value, so pointMap = pointMap.pop() will replace your array reference with a point.
If you want to only have a single point in the array when the checkbox is checked, simply overwrite it:
if (checkboxIsChecked) {
// Only want one point, assign to index 0 (works whether the
// array already has a point or not)
pointMap[0] = theNewPoint;
} else {
// Want to allow multiple points, push the point onto the array
pointMap.push(theNewPoint);
}
If the user can check the checkbox while there are already values in pointMap, you'll want to remove all but the last one when they check it. In your event handler for the checkbox:
if (checkboxIsChecked && pointMap.length > 1) {
// Remove all entries except the last pushed one
pointMap.splice(0, pointMap.length - 1);
}
Related
I'm following an exercise's tutorial but I cannot understand the logic.
The initial setup is a 3x3 board made of a 2D array. Each nested array will have 3 elements, and they will be objects with two properties: x and y, defining their position in the array. (x represent the index and y the position of each element in the nested array so it will be 0,1 or 2 since each nested array has 3 elements).
we define an object we will be using: it will be named player and, for now, will have x and y properties set to the following values:
const player = {
x: 1,
y: 1
};
So now the object is placed in the middle of our 3x3 board with position {1,1}
Our next logical step is to start writing some code and to define a function that will allow us, whenever called, depending on the given command (go left or go right) to change the position of the player on the board. To be able to affect the player’s properties and its position, our function has to have access to the player object. Also, we have to set some condition in order not to allow our player to leave the board. And that's the condition I do not understand:
if (thePlayer.y >= 0 && thePlayer.y < 2) {
if (command === 'l') {
thePlayer.y--;
} else {
thePlayer.y++;
}
console.log(`Player has position: x=${thePlayer.x}, y=${thePlayer.y}`);
} else {
console.log("You can't place player outside of the board!");
}
}
Why the first 'if' statement says >=0 and not >0 ?
if the value of y was 0, the player would be on the left side of the board, and then still able to move left because equal to zero and then would go out of the board.
What makes me totally blank is the following sentence:
Now, if our player’s property “y” is lower than 0, we’ll get a notification that the player left the board, and that is not allowed.
The exercise is right but I would like to understand!
This logic is wrong for me also, You will not be able to move 'player' when it will be on Y = 0, which is still correct position. I guess the better and clear solution will be first check the command and then check if command is valid or not, smthg like this:
const moveHandlers = {
'l': () => thePlayer.y--,
'r': () => thePlayer.y++,
}
const validCommands = Object.keys(moveHandlers);
if (!validCommands.includes(command)) {
throw new Error('Invalid command');
}
const invalidLeftMove = command === 'l' && thePlayer.y === 0;
const invalidRightMove = command === 'r' && thePlayer.y === 2;
if (invalidLeftMove || invalidRightMove) {
throw new Error("You can't place player outside of the board!");
}
moveHandlers[command]();
So, I'm making a game on HTML5 canvas. It's a top down shooter, and I need to create a bullet every time you click to make the character shoot.
Initially, I just prevented the player from firing another bullet until it went out of bounds or it hit an enemy, as seen here. This worked percetly, but of course, makes for uninteresting gameplay.
Then, I began researching about JS classes, and I thought that it would be the key to the problem. I created a bullet class, and moved all the logic for the bullet to the class. Then, I created an instance of it, and called it in other parts of the code to execute its logic. This worked exactly as it did before, which is good, because it meant I could translate the thing I had before to a class, but it had a similar issue.
This is how the class is defined:
class bullet{
constructor(_img, _piercing){
this.bulletPic = document.createElement("img");
this.img = this.bulletPic.src = _img;
this.piercing = _piercing;
}
shoot(){
this.bulletAngle = playerAngle;
this.bulletX = playerX;
this.bulletY = playerY;
bulletShot = true;
shots = 0;
}
draw(){
canvasContext.save();
canvasContext.translate(this.bulletX, this.bulletY);
canvasContext.rotate(this.bulletAngle);
canvasContext.drawImage(this.bulletPic, -this.bulletPic.width / 2, -this.bulletPic.height / 2);
canvasContext.restore();
if(bulletShot){
this.bulletX += Math.sin(this.bulletAngle) * BULLET_SPEED;
this.bulletY -= Math.cos(this.bulletAngle) * BULLET_SPEED;
}
}
}
And here is the object definition:
let bullet1 = new bullet("Textures/player.png", true);
If I want to shoot another bullet at the same time, I need to have already defined a new instance of the bullet class, is there any way for me to define a new instance every time I click?
Edit: The shoot and draw methods are called in another file that follow logic that's not shown here. Mainly what this other code does, is detect when it hits an enemy or when it goes out of bounds to set "bulletShot" to false, that makes it "despawn", and I can shoot another bullet. This is part of the 1 bullet at a time limitation I'm trying to remove here, but that can go once this central issue is fixed.
If I understand your situation, you could use a function that returns a new class:
function bulletFactory( className ) {
return new className();
}
If you want to achieve that there could be several bullets in "mid-air", after a series of fast consecutive clicks, then create an array of bullets. You would initialise that array like this:
const bullets = Array({length: ammo}, () => new Bullet());
ammo would be the number of bullets that the user can shoot in total.
NB: I simplified the call of the constructor. Add the arguments you want to pass. Secondly, it is common practice to start class names with a capital.
Then add a state property in the Bullet instances that indicates whether the bullet is:
Hidden: it is not visible yet, but part of the total ammunition that can still be used in the future
Ready: it is the one bullet that is visible at the start location, ready to be fired by the user
Shot: a bullet that has been shot and is currently flying through the game area
At first this state is "hidden":
constructor(_img, _piercing){
this.state = "hidden";
// ...
}
draw() {
if (this.state === "hidden") return; // Don't draw bullets that are not available
// ...
}
Then at the start of the game, make one bullet visible (where it should be clicked):
bullets[0].state = "ready"; // From now on it will be drawn when `draw()` is called
In the click handler do the following:
// Fire the bullet the user clicked on:
bullets.find(bullet => bullet.state === "ready").shoot(playerAngle, playerX, playerY);
// See if there is a next bullet remaining in the user's ammo:
const nextBullet = bullets.find(bullet => bullet.state === "hidden");
if (nextBullet) nextBullet.state = "ready"; // Otherwise ammo is depleted.
The shoot method should not rely on global variables, but get the necessary external info as arguments:
shoot(playerAngle, playerX, playerY) {
this.bulletAngle = playerAngle;
this.bulletX = playerX;
this.bulletY = playerY;
this.state = "shot";
}
Don't use global variables inside your class methods (shot, ammo,...). Instead use arguments or other instance properties.
The draw method should also work with that state:
draw() {
if (this.state === "hidden") return; // Don't draw bullets that are not available
// ...
if(this.state === "shot") {
this.bulletX += Math.sin(this.bulletAngle) * BULLET_SPEED;
this.bulletY -= Math.cos(this.bulletAngle) * BULLET_SPEED;
}
}
In your animation loop, you should call draw on all bullets. Something like:
bullets.forEach(bullet => bullet.draw());
I did not see any code for when a bullet has left the game area, either by hitting something or just flying out of range. In such case the bullet should be removed from the bullets array to avoid that the draw method keeps drawing things without (visual) significance.
Here is how you could delete a specific bullet:
function deleteBullet(bullet) {
const i = bullets.indexOf(bullet);
if (i > -1) bullets.splice(i, 1);
}
I hope this gets you going on your project.
I ended up making an array that contains multiple instances of the class. I defined a variable that I used as a limit and then set up a for statement to create all the objects, then, I can call them using the array name and the position.
for(var i = 0; i < arraySize; i++){
arrayName[i] = new className(parameters);
}
Examples of usage:
arrayName[5].method();
I am trying to animate a line two lines along a path, one then the other. Basically it will look like one line being drawn, stopping at a point, then another line being drawn somewhere else. So far I have come across promises and callbacks to achieve this, but being a javascript newbie this is confusing
Current animate function:
/*
* Animation function draws a line between every point
*/
var animate = function(p){
return new Promise(function(resolve) {
t = 1;
var runAnimation = function(){
if(t<p.length-1){
context.beginPath();
context.moveTo(p[t-1].x,p[t-1].y);
context.lineTo(p[t].x,p[t].y);
context.stroke();
t++;
requestAnimationFrame(function(){runAnimation()});
} else {
resolve()
}
};
runAnimation();
});
}
Current call to animate function:
animate(points).then(animate(secondary_points));
The points are similar to:
var points = [{x:100, y:200}];
And the paths the lines need to follow are just the multiple coordinates inside points and secondary_points
Ive tried many solutions on SO that were similar, but small differences cause me to either mess up or not understand the solution. The biggest issue I seem to have is calling the SAME animate function, with that animate function needing to be run on different parameters.
Without this solution, using
animate(points);
animate(secondary_points);
the lines are drawn somewhat at the same time, but the result is actually just randomly placed dots along the path instead of smooth lines, I assume because both are running at the same time.
How would I go about fixing this so that one line is drawn along path1 and then the second line is drawn along path2?
It is probably a simple solution, but Ive worked with JS for 3 days and my head is still spinning from getting used to some of the syntax of the old code Ive had to fix
Thank you
EDIT:
The full flow of the animation is as follows:
I have a php file that contains 2 canvases, each containing an image of a map. The php file has a couple <script/> tags, one of which calls the js script I am writing the animation on via drawPath(source,destination,true) or drawPath(source,destination,false)
The drawPath function uses the boolean to determine which canvas to get the context for, and then draw on the path from point A to point B via finding the path and creating the points mentioned above, then drawing using animate(). There are a couple breaks in the maps that require separate lines, which prompted my original question. I was able to fix that thanks to suggestions, but now I am having a larger issue.
If I need to go from point A on map A to point B on map B, ie
drawPath(source, end_point_of_map_A, true); is called then
drawPath(start_point_of_map_B, destination, false);, the lines are drawn only on one map, and they are similar to before where they are 1. random and 2. incomplete/only dots
I am assuming this is due to the animation again, because it worked when just drawing the lines statically, and each animation works when going from point A to B on a single map
Any help is appreciated!
Edit:
DrawPath()
function drawPath(source, desti, flag) {
/*
* Define context
*/
//lower
if(!flag){
var c = document.getElementById("myCanvas");
context = c.getContext("2d");
//upper
} else {
var cUpr = document.getElementById("myCanvasUpr");
context = cUpr.getContext("2d");
}
/*
* Clear the variables
*/
points = [];
secondary_points = [];
vertices = [];
secondary_vertices = [];
t = 1;
done = false;
//check for invalid locations
if (source != "" && desti != "") {
context.lineCap = 'round';
context.beginPath();
/*
* Get the coordinates from source and destination strings
*/
var src = dict[source];
var dst = dict[desti];
/*
* Get the point number of the point on the path that the source and destination connect to
*/
var begin = point_num[source];
var finish = point_num[desti];
/*
* Draw the green and red starting/ending circles (green is start, red is end)
*/
context.beginPath();
context.arc(src[0], src[1], 8, 0, 2 * Math.PI);
context.fillStyle = 'green';
context.fill();
context.beginPath();
context.arc(dst[0], dst[1], 6, 0, 2 * Math.PI);
context.fillStyle = 'red';
context.fill();
/*
* Call the function that draws the entire path
*/
draw_segments(begin, finish, src, dst, flag);
//window.alert(JSON.stringify(vertices, null, 4))
/*
* Edit what the line looks like
*/
context.lineWidth = 5;
context.strokeStyle = "#ff0000";
context.stroke();
}
}
A nice way to handle this is to put your lines into a an array where each element is a set of points of the line. Then you can call reduce() on that triggering each promise in turn. reduce() takes a little getting used to if you're new to javascript, but it basically takes each element of the array c in this case, does something and that something becomes the next a. You start the whole thing off with a resolve promise which will be the initial a. The promise chain will be returned by reduce to you can tack on a final then to know when the whole thing is finished.
For example:
let canvas = document.getElementById('canvas')
let context = canvas.getContext('2d');
var animate = function(p){
return new Promise(function(resolve) {
t = 1;
var runAnimation = function(){
if(t<p.length-1){
context.beginPath();
context.moveTo(p[t-1].x,p[t-1].y);
context.lineTo(p[t].x,p[t].y);
context.stroke();
t++;
requestAnimationFrame(function(){runAnimation()});
} else {
resolve()
}
};
runAnimation();
});
}
// make some points:
let points = Array.from({length: 200}, (_,i) => ({x:i+1, y:i+2}))
let points2 = Array.from({length: 200}, (_,i) => ({x:300-i, y:i+2}))
let points3 = Array.from({length: 200}, (_,i) => ({x:i*2, y:100+100*Math.sin(i/10)}))
// create an array holding each set
let sets = [points, points2, points3]
// use reduce to call each in sequence returning the promise each time
sets.reduce((a, c) => a.then(() => animate(c)), Promise.resolve())
.then(() => console.log("done"))
<canvas id="canvas" height="300" width="500"></canvas>
I am trying to solve the problem of finding the number of paths in a lattice (some grid) using recursion.
I wrote the following code which suppose to find total number of valid paths in a lattice/grid. In a valid path (to the bottom right corner of the grid), you can only move in one of two directions: right or down.
I'm not sure it is correct way of using recursion.
Is there any better way of solving this problem / using recursion to solve it?
Thanks
var array = [[0,0,0,0],[0,0,0,0],[0,0,0,0]];
var lastRowIndex=2;
var lastColIndex=3;
var count=0;
function pathCount(i,j){
if(!(array[i-1]==undefined) || !(array[i][j-1]==undefined)){
if(!(array[i-1]==undefined)){
--i;
pathCount(i,j);
i++;
}
if(!(array[i][j-1]==undefined)){
--j;
pathCount(i,j);
}
}else{
++count;
}
return count;
}
console.log(pathCount(lastRowIndex,lastColIndex));
Your code is fine. basically it does what it should.
Assuming a valid path is constructed only by moving down the grid or moving to the right,
notice you don't actually need the array, just a size parameter and a pointer to the current position traveled by your algorithm.
As any regression problem let us start by something simple, an induction basis.
First notice, in a linear grid (size 1xN or Nx1) there is only one valid path.
Now, if you are at the (i,j) square, you only have 2 possibilities of moving, hence
the total number of paths is a sum of two element:
The number of valid paths from square (i-1,j)
The number of valid paths from square (i,j-1)
(notice a validity check on these argument is needed, since they are bounded by the possible indices of the grid).
so a possible recursion is:
let size = 10;
let grid = {
width: size,
height: size
};
let currentPosition = {
x: size,
y: size
};
function validDecrement(value) {
return (value - 1 >= 1 ? value - 1 : 1);
}
function countValidPaths(gridObject, startingLocation) {
if (startingLocation.x === 1 || startingLocation.y === 1) {
return 1;
} else if (startingLocation.x > 1 && startingLocation.y > 1) {
return countValidPaths(gridObject, {
x: startingLocation.x,
y: validDecrement(startingLocation.y)
}) + countValidPaths(gridObject, {
x: validDecrement(startingLocation.x),
y: startingLocation.y
});
} else {
console.log(`invalid input: grid: ${gridObject}, startingLocation: ${startingLocation}`);
};
}
console.log(`Number of valid paths over a ${grid.height} by ${grid.width} grid is : ${countValidPaths(grid,currentPosition)}`);
Of course - there is a much better approach to solving this method rather than a recursion. the number you are seeking is simply the binomial coefficient C((grid.with-1)+(grid.height-1),(grid.width-1)) - lattice paths counting
I have created a Tic Tac Toe game using Javascript. Live demo can be seen here
The code works fine but it has a little bug. When I click on the canvas, the image is drawn and everything works fine. But as I click on canvas, I invoke the superclick() function which takes random path that hasn't been clicked on yet and draws set picture (O or X) on set position.
function superclick() {
var filt = paths.filter(function(x) {
return x.avaible == true;
});
var rand = filt[getRandomIntInclusive(0, paths.length - 1)];
/*var evt = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
clientX: (canvas.offsetLeft + rand.x)+5,
clientY: (canvas.offsetTop + rand.y)+5
});
canvas.dispatchEvent(evt)*/
ctx.drawImage(turn[turns],rand.x,+ rand.y);
score[turns] += rand.id;
rand.avaible = false;
turns = turns == "x" ? "o" : "x";
}
The problem here is that sometimes this function is invoked, and sometimes it isn't. When it isn't invoked it throws an error "Cannot read property 'x' of undefined", andI can't understand why. When I try to console.log set random path x it works. Did I miss something?
You're getting a random number between 0 and paths.length (so 0 and 8), but youre using that index to access "filt[]" which is only those squares that haven't been filled in. So you might try to get the 8th element of "filt[]" when only 4 remain.
Try
var rand = filt[getRandomIntInclusive(0, filt.length - 1)];
This will make the max random number you grab proportional to the number of remaining spaces.
Your "filt" variable is an array of object that begins with (at game launch) with 9 items -- one for each square. You are filtering the items as you go based on "avaible" (sic) squares.
Since the length of the paths array does not change (9), getting a random index (via 'getRandomIntInclusive') based on the range 1..9 returns an out of bound error because the length of the filt array is reduced on every turn.
What you want to do is change:
var rand = filt[getRandomIntInclusive(0, paths.length - 1)];
To:
var rand = filt[getRandomIntInclusive(0, filt.length - 1)];
...and you should be in a happy place.