Unexpected indexOf behavior with multidimensional array - javascript

I am attempting to make the classic snake game in p5.js. I have a snake object and I'm storing the locations of its body in a 2d array, this.data, where each element stores an x value and a y value (at index 0 and 1 respectively). As the snake moves, I push new positions into the array.
I ran into a problem when I tried to detect whether or not the snake had run into itself. What I tried to do was test whether its current position was already in the array using indexOf, reasoning that if the new location was open, it would only occur once in the array, at index one less than the array's length. Otherwise, if the location already existed elsewhere in the array (indicating the snake had run into itself), it would return a value less than the length minus one.
However, this doesn't seem to be happening.
function Snake()
{
this.x; //x-coordinate of head
this.y; //y-coordinate of head
this.dx; //velocity in x-direction
this.dy; //velocity in y-direction
this.length; //length of snake
this.data; //stores all locations snake occupies
this.alive = 1; //is the snake alive?
this.update = function(board)
{
if (this.alive)//update head position
{
this.x += this.dx;
this.y += this.dy;
let tempCoords = [this.x,this.y];
this.data.push(tempCoords);
while (this.data.length > this.length) //janky
{
this.data = this.data.slice(1);
}
if (this.data.indexOf(tempCoords) + 1 != this.data.length) //make sure snake hasn't hit itself
{
this.alive = 0;
}
}
}
}
The final if statement always evaluates false even when the snake intersects itself. From the testing I've done, this seems to be an issue with using indexOf on multidimensional arrays. What solutions are there to this problem?

Essentially, you have the following data and you want to see if the data in head is equal to any of the elements in points
var points = [[1,1],[1,2],[1,3]]
var head = [1,2]
You can check for any matches in an array using Array.some() like this:
var overlap = points.some(p => p[0] === head[0] && p[1] === head[1])
var points = [[1,1],[1,2],[1,3]]
var head = [1,2]
var overlap = points.some(p => p[0] === head[0] && p[1] === head[1])
console.log(overlap)

indexOf uses an equality check to find the index, and
[0, 0] === [0, 0]
is false, as objects (and arrays are objects), are compared by reference (and you do have two different arrays). To compare them by their inner values you have to manually check the x and ys against each other:
const collides = this.data.some(coords => coords[0] === this.x && coords[1] === this.y);

Related

Is there a way to see if a value in a 2D array matches any value in another 2D array?

I'm building a battleship game in Javascript and React and I've been stuck on this issue for a while now even after much Googling and StackOverflowing.
Basically my board is a 2D array, with 10 arrays inside of one array. I'm trying to randomly place ships and I'm having difficulties checking if a ship intersects another ship.
Here's what I have for my ships:
placeShips = () => {
// Logic to place boats randomly below
// Checks required before placing a boat:
// 1. Does the boat go off the board
// 2. Does the boat overlap another boat
// 3. If checks above pass then place boat
let placedPosition = []
let board = this.state.board.slice()
let i
for (i = 0; i < this.state.ships.length; i++) {
// First randomly select coordinates for where the boat will start
let xcoord = Math.floor(Math.random() * 10)
let ycoord = Math.floor(Math.random() * 10)
// Get positions in array where a boat will be
let potentialBoat = []
let newCoords
let j
for (j = 0; j < this.state.ships[i].getLength(); j++) {
newCoords = [xcoord, ycoord + j]
potentialBoat.push(newCoords)
The first for loop repeats for each ship left in my state to place and the second for loop takes a ship's length, gets the intended coordinate ([[0, 1], [0,2]] for a 2 length ship for example) and stores it in the potentialBoat array.
My idea is to use this potentialBoat array and see if there's any [xcoordinate, ycoordinate] that exists already in the placedPosition array and if so, to loop again for the current boat and get new coordinates until they don't intersect.
Is this possible? Or should I rethink my entire implementation? Thanks!
Inside the inner loop, when in the process of creating a ship, consider creating a string representing the coordinates. Eg, for newCoords of 1, 3, create a string 1_3. To validate the location, check to see if that string exists in an array (or Set) of the locations of the validated ships. At the end of the inner loop, once all positions for the length of the ship have been validated, combine the possible-locations into the validated-locations array:
placeShips = () => {
const placedPosition = [];
const board = this.state.board.slice();
const validatedPositionStrings = []; // <---- Create this array
for (const ship of this.state.ships) {
const thisShipLength = ship.getLength();
tryShip:
while (true) {
const thisBoatPossiblePositionStrings = [];
// Generate ship positions until valid
const xcoord = Math.floor(Math.random() * 10);
const ycoord = Math.floor(Math.random() * 10);
const potentialBoat = [];
for (let j = 0; j < thisShipLength; j++) {
// Then check to see if the below position is already in it
const thisCoordinateString = `${x}_${y}`;
if (validatedPositionStrings.includes(thisCoordinateString)) {
// Invalid
continue tryShip;
}
thisBoatPossiblePositionStrings.push(thisCoordinateString);
// If this point is reached, then this particular coordinate is valid
// do whatever you need to do:
const newCoords = [xcoord, ycoord + j];
potentialBoat.push(newCoords);
}
// All positions for ship are valid
// do something with potentialBoat here?
// push positions to placedPosition?
validatedPositionStrings.push(...thisBoatPossiblePositionStrings);
break;
}
}
}
It could be made less computationally complex by using a Set instead of an array, but that probably doesn't matter unless there are a very large number of iterations.
It would also be possible to search your array of arrays to see if the position has already been placed, but that would require an unnecessary amount of code IMO.
If possible, you might consider changing your data structure around so that rather than an array of arrays, you have just a single object representing the coordinates, whose values indicate the ship at that position (and possibly other attributes needed for a particular point), eg:
{
1_3: { ship: 'destroyer', 'attackedYet': 'false' }
// ...
Such an object would probably be easier to look up and work through than an array of arrays of X-Y pairs.

Javascript collision of two point array polygons

I have searched all over, and I have found answers for rectangle circle and sprite collisions. Nothing that provides collision detection between two arrays of points like for example,
var poly1=[
[0,0],
[20,50],
[50,70],
[70,20],
[50,0]
];
// each point from one to the next represent a line in the shape, then the last point connects to the first to complete it.
var poly2=[
[50,30],
[40,90],
[70,110],
[90,70],
[80,20]
];
var collided=arraysCollided(poly1,poly2);
Does anyone know of a library that can do just this? My research has come up with nothing that supports just that, and isnt associated with some game engine library.
For example a collision is triggered true when one or more points is inside the polygon of the other.
SAT.js was the anser for me, I just put every point into SAT.Vector then into SAT.Polygon, then test them with SAT.testPolygonPolygon(SAT.Polygon,SAT.Polygon);
var poly1={
name:"poly2",
x:400,
y:60,
rotation:0,
runner:function(){
},
points:[
[20,-50],
[-30,-50],
[-30,30],
[10,60],
[50,20]
]
};
var poly2={
name:"poly2",
x:50,
y:70,
runner:function(){
this.x+=1;
},
points:[
[-20,-40],
[-60,50],
[10,70],
[50,30],
[30,-20]
]
};
pGon=(poly)=>{
var center=SAT.Vector(0,0);
var pts=[];
for(var i in poly.points){
var point=poly.points[i];
// point = [0:x,1:y]
pts[pts.length]=new SAT.Vector(point[0]+poly.x,point[1]+poly.y);
}
var poly_a=new SAT.Polygon(center,pts);
return poly_a;
};
pCollide=(p1,p2)=>{
var p1_poly=pGon(p1);
var p2_poly=pGon(p2);
var res=new SAT.Response();
var collided=SAT.testPolygonPolygon(p1_poly,p2_poly,res);
console.log(collided);
console.log(res);
return collided;
};
var collided=pCollided(poly1,poly2);
With that, it maps each point to a polygon on the coordinate system, then tests it from there. So collided = true
I checked for if each point of each polygon is in the other polygon. This is the code for checking if a point is in a polygon:
function pip(x, y, polygon) {
let odd = false;
let v = polygon.va; //The vertices array
let j = v.length - 2;
for (let i=0; i<v.length-1; i+=2) {
if ((v[i+1]<= y && v[j+1]>=y || v[j+1]<= y && v[i+1]>=y)
&& (v[i]<=x || v[j]<=x)) {
odd ^= (v[i] + (y-v[i+1])*(v[j]-v[i])/(v[j+1]-v[i+1])) < x;
}
j=i;
}
if(odd === false) odd = 0;
return odd;
}
I got this from Here, but modified it to work for an array like this [x1,y1,x2,y2,x3,y3...]. To make it work for x,y pairs, you would just change the for loops modifier thing and look at polygon.va[i][0] as x and polygon[i][1] as y.

p5.js code doesn't throw errors, but won't load on mouse click

I'm doing an analysis of the presidential candidates speeches. I have a data file with the following variables:
> names(cl.context)
[1] "id" "category" "statement" "nchar" "polarity"
The statement variable is populated by sentences that each belong to one category from three. The polarity ranges from -1 to 1, reflecting whether the sentence has a positive bias, neutral, or negative bias.
What I'm trying to do in p5 is to have statements displayed by category, with random x,y placement, when the user clicks the mouse INSIDE the canvas. The statements themselves are colored according to their polarity.
I finally got to the point where the developer console doesn't throw any errors and it draws the canvas. But when I click within the canvas, nothing happens. No statements appear.
I'm VERY new to JavaScript, and since it's not throwing an error message, I can't resolve where the issue lies. Hoping for some advice here.
My p5 code:
var clContext;
var x;
var y;
const STATEMENTS = 118, CATEGORY = 3, QTY = STATEMENTS/CATEGORY | 0,
POLARITY = 3,
statements = Array(STATEMENTS), inds = Array(CATEGORY), polarity = Array(POLARITY);
//load the table of Clinton's words and frequencies
function preload() {
clContext = loadTable("cl_context.csv", "header");
}
function setup() {
createCanvas(647, 400);
background(51);
// Calling noStroke once here to avoid unecessary repeated function calls
noStroke();
// iterate over the table rows
for(var i=0; i<clContext.getRowCount(); i++){
//- Get data out of the relevant columns for each row -//
var inds = clContext.get(i, "category");
var statements = clContext.get(i, "statement");
var polarity = clContext.get(i, "polarity")
}
for (let i = 0; i < statements; randomCategoryStates(i++));
// create your Statement object and add to
// the statements array for use later
inds[i] = new Statement();
console.info(inds);
}
function draw() {
if(mouseClicked == true){
for(var i=0; i<inds.length; i++) {
inds[i].display();
}
}
}
function mouseClicked() {
if((mouseX < width) && (mouseY < height)) {
randomCategoryStates(~~random(CATEGORY));
redraw();
return false;
}
}
// Function to display statements by a random category with each mouse click
function randomCategoryStates(group) {
let idx = inds[group], rnd;
while ((rnd = ~~random(QTY)) == idx);
inds[group] = rnd;
}
// Function to align statements, categories, and polarity
function Statement() {
this.x = x;
this.y = y;
this.xmax = 10;
this.ymax = 4;
this.cat = inds;
this.statement = statements;
this.polarity = polarity;
// set a random x,y position for each statement
this.dx = (Math.random()*this.xmax) * (Math.random() < .5 ? -1 : 1);
this.dy = (Math.random()*this.ymax) * (Math.random() < .5 ? -1 : 1);
}
// Attach pseudo-class methods to prototype;
// Maps polarity to color and x,y to random placement on canvas
Statement.prototype.display = function() {
this.x += this.dx;
this.y += this.dy;
var cols = map(this.polarity == -1, 205, 38, 38);
var cols = map(this.polarity == 0, 148, 0, 211);
var cols = map(this.polarity == 1, 0, 145, 205);
fill(cols);
textSize(14);
text(this.statement, this.x, this.y);
};
EDIT: One thing that confused me is that the help I got with this code on the Processing forum didn't include a call for the mouseClicked() function in the draw function, so I added that. Not entirely sure that I did it correctly or if it was even necessary.
Your code has a lot going on. I'm going to try to go through everything, in no particular order:
Why do you need these variables?
var x;
var y;
I know that you think you're using them to pass in a position to a Statement, but you never set these variables! Let's just get rid of them for now, since they aren't doing anything. This will cause an error in your code, but we'll fix that in a second.
Look at this for loop:
for(var i=0; i<clContext.getRowCount(); i++){
//- Get data out of the relevant columns for each row -//
var inds = clContext.get(i, "category");
var statements = clContext.get(i, "statement");
var polarity = clContext.get(i, "polarity")
}
Here you're reading in the CSV file, but then you aren't doing anything with these variables. You then follow that up with this:
for (let i = 0; i < statements; randomCategoryStates(i++));
// create your Statement object and add to
// the statements array for use later
inds[i] = new Statement();
Notice the semicolon after that for loop! That means the inds[i] = new Statement() line is outside the loop, which doesn't make any sense. I also don't really know what you're doing with the randomCategoryStates(i++) part.
You need to combine all of that into one loop:
for (var i = 0; i < clContext.getRowCount(); i++) {
var category = clContext.get(i, "category");
var statement = clContext.get(i, "statement");
var polarity = clContext.get(i, "polarity")
inds[i] = new Statement();
}
But this still doesn't make any sense, because you're never passing those variables into your Statement class. So let's take a look at that.
I'll just add some comments:
function Statement() {
this.x = x; //when do you ever set the value of x?
this.y = y; //when do you ever set the value of y?
this.cat = inds; //cat is the array that holds all statements? What??
this.statement = statements; //statement is the statements array, but nothing is ever added to that array?
this.polarity = polarity; //when do you ever set the value of polarity?
As you can see, what you're doing here doesn't make a lot of sense. You need to change this constructor so that it takes arguments, and then you need to pass those arguments in. Something like this:
function Statement(category, polarity, statement) {
this.category = category;
this.statement = statement;
this.polarity = polarity;
}
Now that we have that, we can change the line in our for loop to this:
inds[i] = new Statement(category, statement, polarity);
But that still doesn't make sense. Why do you have separate arrays for statements, categories, and polarities? Don't you just want one array that holds them all, using instances of the Statement class? So let's get rid of the inds and polarity variables, since they aren't used for anything.
We then change that line to this:
statements[i] = new Statement(category, polarity, statement);
We also have to change everywhere else that's still using the inds variable, but we have other problems while we're at it.
Let's just start with your draw() function:
function draw() {
if (mouseClicked == true) {
for (var i = 0; i < statements.length; i++) {
statements[i].display();
}
}
}
So I guess you only want anything to display while the mouse is pressed down, and nothing to display when the mouse is not pressed down? I'm not sure that makes sense, but okay. Even so, this code doesn't make sense because mouseClicked is a function, not a variable. To determine whether the mouse is pressed, you need to use the mouseIsPressed variable, and you don't need the == true part.
if (mouseIsPressed) {
I have no idea what these two functions are supposed to do:
function mouseClicked() {
if ((mouseX < width) && (mouseY < height)) {
randomCategoryStates(~~random(CATEGORY));
redraw();
return false;
}
}
// Function to display statements by a random category with each mouse click
function randomCategoryStates(group) {
let idx = statements[group],
rnd;
while ((rnd = ~~random(QTY)) == idx);
statements[group] = rnd;
}
There are much simpler ways to get random data. I'm just going to delete these for now, since they're more trouble than they're worth. We can go back and add the random logic later.
For now, let's look at the display() function inside your Statement class:
Statement.prototype.display = function() {
this.x += this.dx;
this.y += this.dy;
var cols = map(this.polarity == -1, 205, 38, 38);
var cols = map(this.polarity == 0, 148, 0, 211);
var cols = map(this.polarity == 1, 0, 145, 205);
fill(cols);
textSize(14);
text(this.statement, this.x, this.y);
};
We never actually declared the x, y, dx, or dy, variables, so let's add them to the constructor:
this.x = random(width);
this.y = random(height);
this.dx = random(-5, 5);
this.dy = random(-5, 5);
Back to the display() function, these lines don't make any sense:
var cols = map(this.polarity == -1, 205, 38, 38);
var cols = map(this.polarity == 0, 148, 0, 211);
var cols = map(this.polarity == 1, 0, 145, 205);
Why are you declaring the same variable 3 times? Why are you trying to map a boolean value to a number value? This doesn't make any sense. For now, let's just get rid of these lines and simplify your logic:
if(this.polarity == -1){
fill(255, 0, 0);
}
else if(this.polarity == 1){
fill(0, 255, 0);
}
else{
fill(0, 0, 255);
}
This will make negative polarity red, positive polarity green, and neutral polarity blue.
Now that we have this, we can actually run the code. When you hold the mouse down, you'll see your statements display and move around randomly. However, they'll quickly fill up your screen because you aren't ever clearing out old frames. You need to call the background() function whenever you want to clear out old frames. We might do that at the beggining of the draw() function, or right inside the if(mouseIsPressed) statement, before the for loop.
if (mouseIsPressed) {
background(51);
for (var i = 0; i < statements.length; i++) {
If you make all those changes, you will have a working program. I'd be willing to bet that it still doesn't do exactly what you want. You're going to have to start much simpler. Your code is a bit of a mess, and that's a result of trying to write the whole program all at once instead of testing small pieces one at a time. This is why we ask for an MCVE, because debugging the whole thing like this is very painful. You need to start narrowing your goals down into smaller pieces.
For example, if you now want to make it so only one statement appears at a time, start over with a simpler example sketch that only shows one hardcoded statement. Get that working perfectly before you try to integrate it into your main program. If you want the statements to be ordered by category, then start over with a simpler example sketch that only shows statements based on category, without any of the extra logic. That way if you have a question about something specific, you can post that small code and it will be much easier to help you.
Good luck.

Comparing values within an array to each other

I am new to JavaScript and HTML and am working on a small game.
I have four 'enemies' whose position on the canvas is determined by the values in the arrays 'enemyX' and 'enemyY'.
Very simply, I want to detect if the enemies have 'collided', i.e have moved within 30px of each other (the enemy image is 30px by 30px).
What I want to do is subtract the value of the i th value in the array with the other values in the same array and see if this value is less than 30. The less than 30 part is an if statement, so how do I go about subtracting all the values from each other without many lines of code?
Here's what I have tried based on the answers below:
var count = 0;
var innercount = 0;
while (count <= 3) {
while (innercount<=3) {
collisionDetect(count, innercount, enemyX[count], enemyY[count], enemyX[innercount], enemyY[innercount])
innercount++
}
count++
}
var i = 0;
while (i < enemyX.length) {
if (collisionX[i] == 1) {
directionX = directionX*-1;
}
if (collisionY[i] == 1) {
direction = directionY*-1;
}
}
}
}
function collisionDetect(count, innercount, x, y, xX, yY ) {
if ((Math.abs(x-xX)) <=30) {
collisionX[count] = 1
collisionX[innercount] = 1
}
if ((Math.abs(y - yY)) <=30) {
collisionY[count] = 1
collisionY[innercount] = 1
}
return collisionX, collisionY;
}
This code gives me a blank canvas.
Detection of an intersection between two objects (assuming rectangular shape) and the position defines the center of the object.
function getRect(x, y, w, h)
{
var left = x - Math.floor(w / 2),
top = y - Math.floor(h / 2);
return {
left: left,
top: top,
right: left + w,
bottom: top + h
};
}
function intersects(A, B)
{
return A.right >= B.left &&
A.left <= B.right &&
A.bottom >= B.top &&
A.top <= B.bottom;
}
alert(intersects(getRect(12, 56, 30, 30), getRect(30, 40, 30, 30))); // true
The getRect() function can be modified to accommodate for a different anchor position, e.g. top left.
You could use a function:
function colliding(x1, y1, x2, y2){
return Math.abs(x1-x2) <= 30 && Math.abs(y1-y2) <= 30;
}
then use the function to test the different combination of enemies: (1,2), (1,3), (1,4), (2,3), (2,4), and (3,4).
So, for example, you would use: colliding(enemyX[2], enemyY[2], enemyX[3], enemyY[3]) to check if enemy 2 and 3 are colliding. Do that with all the combinations above.
EDIT: to make it more readable, you could define an additional function:
function enemiesColliding(e1, e2){
return colliding(enemyX[e1], enemyY[e1], enemyX[e2], enemyY[e2])
}
and then use it:
enemiesColliding(1,2) || enemiesColliding(1,3) || enemiesColliding(1,4) ||
enemiesColliding(2,3) || enemiesColliding(2,4) || enemiesColliding(3,4)
I am going to restate my understanding of you question, just so there is no confusion.
You have two arrays in paralleled , one for x cords, and one for y cords. Each ship has a element in both arrays.
So, for example, ship 12 could be found at xPos[12] and yPos[12], where xPos and yPos are the arrays from above.
It also stands to reason that this a communicative. If ship[a] collides with ship[b] then ship[b] has collided with ship[a]. And I think that hold for 3 + ships.
I would start by writing a distance function.
dist(x1,y1,x2,y2)
{
return Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
Then I would write code run through the arrays.
Every element must be compared to every other element, one time.
var counter = 0;
var indexOfShipThatHaveCollided = [];
while(counter < Xpos.length)
{
var innerCounter = counter;
while(innerCounter < Xpos.length)
{
t = dist(Xpos[counter],Ypos[counter],Xpos[innerCounter],Ypos[innerCounter])
if(t < 30)
{
indexOfShipThatHaveCollided.push(counter)
indexOfShipThatHaveCollided.push(innerCounter)
}
}
}
The code above compares every ship to every other ship ONCE.
It will compare ship[1] to ship[8], but it will not compare ship[8] to ship[1].
I did not test ANY of this code, but I hope it moves you in the right direction.
If you have anymore question just give me a comment.
According my understanding of your question below is my solution:
I think you need to find distance between two points. by using distance you can apply your logic.
To find distance following is the formula:
Given the two points (x1, y1) and (x2, y2),
the distance between these points is given by the formula:

How to create a data model for a boat?

How would one create a model for a boat in javascript that exists as a grid reference in a cartesian plane?
I would like to learn javascript by creating clone of the popular game Battleship!
To this end I need assistance in my quest to start programming boats!
Here's something to get you started:
function Boat(name, length) {
this.name = name
this.pegs = new Array(length)
this.sunk = false
}
Boat.prototype.place = function (x, y, orientation) {
// Before calling this method you'd need to confirm
// that the position is legal (on the board and not
// conflicting with the placement of existing ships).
// `x` and `y` should reflect the coordinates of the
// upper-leftmost peg position.
for (var idx = 0, len = this.pegs.length; idx < len; idx++) {
this.pegs[idx] = {x: x, y: y, hit: false}
if (orientation == 'horizontal') x += 1
else y += 1
}
}
Boat.prototype.hit = function (x, y) {
var sunk = true
var idx = this.pegs.length
while (idx--) {
var peg = this.pegs[idx]
if (peg.x == x && peg.y == y) peg.hit = true
// If a peg has not been hit, the boat is not yet sunk!
if (!peg.hit) sunk = false
}
return this.sunk = sunk // this is assignment, not comparison
}
Usage:
var submarine = new Boat('submarine', 3)
submarine.place(2, 6, 'horizontal')
submarine.hit(2, 6) // false
submarine.hit(3, 6) // false
submarine.hit(4, 6) // true
Storing pegs as objects with x, y, and hit keys is not necessarily the best approach. If you wanted to be clever you could, for example, store the upper-leftmost coordinates on the object along with the orientation. Then, the hits could be stored in an array. Something like:
name: 'submarine'
x: 2
y: 6
orientation: 'horizontal'
pegs: [0, 0, 0]
After a hit at (2, 6), the boat's properties would be:
name: 'submarine'
x: 2
y: 6
orientation: 'horizontal'
pegs: [1, 0, 0]
I'd start off by creating an array (or two, one for each side) to hold the boats. This can be pretty simple, and just use the boat number as the array entry for "filled" positions.
My boat model would have a length (n "pegs"), a position (x, y), an orientation (vertical or horizontal), and a hit counter. Another option would be to just store each array position the boat occupies, which would make some stuff a little easier.

Categories

Resources