Collision Script in javascript help needed - javascript

Alright so I just tried to cut down on lines of code by changing manually writing out everything into an array.. My problem is that he now teleports and gravity doesnt work...
first of all I have a grid of cell objects which basically are a 32x32 grid "640X480".
These objects are all passed onto an array like so-
var gridcellarray = [750];
gridcellarray[0] = cell0;
gridcellarray[1] = cell1;
gridcellarray[2] = cell2;
and so on for 750 32x32 cells...
Now as for the collision script I have this...
function collisioncheck(obj) {
obj = obj;
for(var i = 0; i < gridcellarray.length; i++){
//really long if statement// sorry...
if ((gridcellarray[i].solid == true) && ((obj.PosY >= gridcellarray[i].y - obj.maskImg.height) && !(obj.PosY >= gridcellarray[i].y ) && !((obj.PosX > gridcellarray[i].x + solidOriginX + solidImg.width/2-5) || (obj.PosX < gridcellarray[i].x - solidOriginX - solidImg.width/2)))){
if(obj.onground == 0){
obj.PosY = gridcellarray[i].y - obj.maskImg.height;
obj.VelY = 0;
obj.onground = 1;
obj.jump = 0;
}
}
else if (obj.PosY >= canvas.height - obj.maskImg.height){
if(obj.onground == 0){
obj.VelY = 0;
obj.onground = 1;
obj.jump = 0;
obj.PosY = canvas.height - obj.maskImg.height;
}
}
else {
obj.VelY += 1;
obj.onground = 0;
}
}
}
now this code worked just fine before If I had manually copied it 750 times for each cell
and the problem is that Now that I have one iteration of it cycling through them as an array it gets confused and teleports me to the bottom and If I try to jump even when not below or on a block it teleports me back to the bottom.
//edit
I think all the variables should explain their purpose by name but if you have any questions please ask.
//edit
All Variables apply the object your checking collision for such as collisioncheck(player)
here is a link to a running demo of the code... Zack Bloom's code is in it and it works great applied to the unposted horizontal collision scripts but vertically, it wont reset and acknowledge your standing on a block ie ongroud = true;
Demo link
//edit
Ok now that Zack pointed out resetting the y to and x amount it helped alot but he is still not able to jump, as the onground variable doesnt want to reset when the collision is detected... oh and the teleporting is due to my upwards script -
//Upwards Collision//
if ((cell.solid == true) && ((obj.PosY >= cell.y - 32) && !(obj.PosY > cell.y+32) && !((obj.PosX > cell.x + solidOriginX + solidImg.width/2-5) || (obj.PosX < cell.x - solidOriginX - solidImg.width/2)))){
if (obj.onground == 0){
obj.VelY += 1;
obj.onground = 0;
obj.PosY = cell.y + obj.maskImg.height-13;
}
}
Any Ideas on how to fix THIS mess above? to stop him from teleporting? It is only meant to check if the top of the collision mask(red rectangle) is touching the block as if trying to jump through it, but it is meant to stop that from happening so you hit your head and fall back down.
Thanks in Advance!

The else if / else really don't belong in the loop at all, they don't evaluate the cell being looped over, but will be triggered many times each time collisioncheck is called.
function collisioncheck(obj) {
for(var i = 0; i < gridcellarray.length; i++){
var cell = gridcellarray[i];
if (cell.solid && ((obj.PosY >= cell.y - obj.maskImg.height) && !(obj.PosY >= cell.y ) && !((obj.PosX > cell.x + solidOriginX + solidImg.width/2-5) || (obj.PosX < cell.x - solidOriginX - solidImg.width/2)))){
if(!obj.onground){
obj.PosY = cell.x - obj.maskImg.height;
obj.VelY = 0;
obj.onground = 1;
obj.jump = 0;
break;
}
}
}
if (obj.PosY >= canvas.height - obj.maskImg.height){
if(!obj.onground){
obj.VelY = 0;
obj.onground = 1;
obj.jump = 0;
obj.PosY = canvas.height - obj.maskImg.height;
}
} else {
obj.VelY += 1;
obj.onground = 0;
}
}
But an even better way of doing it would be to store each gridcell based on where it was, so you just have to look up the gridcells near the ship.
// You only have to do this once to build the structure, don't do it every time you
// need to check a collision.
var gridcells = {};
for (var i=0; i < gridcellarray.length; i++){
var cell = gridcellarray[i];
var row_num = Math.floor(cell.PosX / 32);
var col_num = Math.floor(cell.PosY / 32);
if (!gridcells[row_num])
gridcells[row_num] = {};
gridcells[row_num][col_num] = cell;
}
// Then to check a collision:
function collisioncheck(obj){
// I'm not sure exactly what your variables mean, so confirm that this will equal
// the width of the object:
var obj_width = solidImg.width;
var obj_height = obj.maskImg.height;
var collided = false;
var left_col = Math.floor(obj.PosX / 32),
right_col = Math.floor((obj.PosX + obj_width) / 32),
top_row = Math.floor(obj.PosY / 32),
bottom_row = Math.floor((obj.PosY + obj_height) / 32);
for (var row=top_row; row <= bottom_row; row++){
for (var col=left_col; col <= right_col; col++){
var cell = gridcells[row][col];
if (cell.solid){
collided = true;
if (row == top_row){
// We collided into something above us
obj.VelY = 0;
obj.PosY = cell.PosY + 32;
} else if (row == bottom_row && !obj.onground){
// We collided into the ground
obj.PosY = cell.x - obj_height;
obj.VelY = 0;
obj.onground = 1;
obj.jump = 0;
}
if (col == left_col){
// We collided left
obj.VelX = 0;
obj.PosX = cell.PosX + 32;
} else if (col == right_col){
// We collided right
obj.VelX = 0;
obj.PosX = cell.PosX - obj_width;
}
}
}
}
if (obj.PosY >= canvas.height - obj_height){
if (!obj.onground){
obj.VelY = 0;
obj.onground = 1;
obj.jump = 0;
obj.PosY = canvas.height - obj_height;
}
}
if (!collided){
obj.VelY += 1;
obj.onground = 0;
}
}
Rather than looping through 720 cells each frame, we are only looking at the cells we know we are overlapping. This is much more efficient and easier to read than all the position calculations.

Some comments on your code:
var gridcellarray = [750];
That creates an array with a single member that has a value of 750. I think you are assuming that it is equivalent to:
var gridcellarray = new Array(750);
which creates an array with a length of 750. There is no need to set the size of the array, just initialise it as an empty array and assign values.
var gridcellarray = [];
gridcellarray[0] = cell0;
This replaces the value of the first member with whatever the value of cell0 is.
function collisioncheck(obj) {
obj = obj;
There is no point in the second line, it just assigns the value of obj to obj (so it's redundant).
for(var i = 0; i < gridcellarray.length; i++){
It is much more efficient in most browsers to save the value of gridcellarray.length, otherwise it must be looked up every time (the compiler may not be able to work whether it can cache it), so:
for (var i = 0, iLen = gridcellarray.length; i < iLen; i++) {
It is more efficient to store a reference to gridcellarray[i] rather than look it up every time. Also, you can use a short name since it's only used within the loop so its purpose is easily found:
var c = gridcellarray[i];
so not only will the code run faster, but the if statement has fewer characters (and you might prefer to format it differently):
if ((c.solid == true) &&
((obj.PosY >= c.y - obj.maskImg.height) && !(obj.PosY >= c.y ) &&
!((obj.PosX > c.x + solidOriginX + solidImg.width/2-5) ||
(obj.PosX < c.x - solidOriginX - solidImg.width/2)))) {
Wow, that really is some if statement. Can you break it down into simpler, logical steps?
Ah, Zack has posted an answer too so I'll leave it here.

Related

Recursive backtracking - Maze generator

I'm trying to generate random maze using DFS algorithm, and I after watching several videos, I still can't get my head over it. I know how DFS algorithm works, but I have no idea, how to use this algorithm to actually create the maze.
There is the code that I'm using (Recursive Implementation):
const dirRow = [-1,+1,0,0];
const dirCol = [0,0,+1,-1];
// Given a current cell as a parameter
async function recursiveBacktracking(x,y){
// While the current cell has any unvisited neighbour cells
for (let i = 0; i < 4; i++){
let newX = x + dirRow[i];
let newY = y + dirCol[i];
if (newX >= 20 || newX < 0) continue;
if (newY >= 20 || newY < 0) continue;
if (values[newY][newX].visited === true) continue;
// Mark the current cell as visited
values[newY][newX].visited = true;
// Remove the wall between the current cell and the chosen cell
if (values[newY][newX].neighbours[i] != null)
values[newY][newX].neighbours[i].val = -1
// Animation
await renderPath(0,newX + newY*20);
// Choose one of the unvisited neighbours
await recursiveBacktracking(newX,newY);
}
}
I'm trying to follow the steps from the wiki page,
and the part that I don't get, is that I have no idea which wall to remove on my 2d grid.
For the actual grid, I'm using 2d array with Node class on each X.
function Node(val, neighbours){
this.val = val;
this.neighbours = neighbours;
this.visited = false;
}
function createNodeMap(w,h){
let grid = []
for (let y = 0; y < h; y++){
grid.push([]);
for (let x = 0; x < w; x++){
grid[y].push(new Node(0, []));
}
}
for (let y = 0; y < h; y++){
for (let x = 0; x < w; x++){
let neighbours= new Array(4).fill(null);
if (x-1 >= 0) neighbours[0] = grid[y][x-1];
if (x+1 < 20) neighbours[1] = grid[y][x+1];
if (y+1 < 20) neighbours[2] = grid[y+1][x];
if (y-1 >= 0) neighbours[3] = grid[y-1][x];
grid[y][x].neighbours = neighbours;
}
}
return grid;
}
Grid that is created (Grey columns are the walls):
EDIT:
Well the part where I'm marking the cell as part of the maze is wrong, because I'm not actually making any space for the walls to be in.

Why does a tetris piece fall all at once instead of one at a time?

I am making tetris in JS. When making a block fall, it makes the block reach the bottom of the screen in one draw instead of slowly approaching the bottom. I tried creating a variable that stores the changes to be made so that it only looks at the current board, but no luck. After checking whether the output variable is == to the board, it seems like the board is changing after all, as it returns true. What's going on?
EDIT: I have successfully made a shallow copy of the array. It still falls to the bottom immediately, though. What's going on?
var data = [];
function array(x, text) {
var y = [];
for (var i = 0; i < x-1; i++) {y.push(text);}
return y;
}
for (var i=0; i<20; i++){data.push(array(10, "b"));}
function draw(){
var j;
var i;
var dataOut = [...data];
for (i = 0; i < data.length - 1; i++){
for (j = 0; j < data[i].length; j++){
if (data[i][j] == "a" && data[i + 1][j] == "b" && i < data.length - 1) {
dataOut[i][j] = "b";
dataOut[i + 1][j] = "a";
}
}
}
data = dataOut;
}
data[0][4] = 'a';
draw();
console.log(data);
In JavaScript, Arrays and Objects are passed by reference. So when you do this:
var dataOut = data;
Both of these references point to the same Array. You could clone the Array every time:
var dataOut = JSON.parse(JSON.stringify(data));
Or simply revert your loop, to go from the bottom to the top. I took the liberty of renaming the variables to make this more clear. Try it below:
var chars = {empty: '.', block: '#'},
grid = createEmptyGrid(10, 20);
function createEmptyGrid(width, height) {
var result = [], x, y;
for (y = 0; y < height; y++) {
var row = [];
for (x = 0; x < width; x++) {
row.push(chars.empty);
}
result.push(row);
}
return result;
}
function draw() {
var x, y;
for (y = grid.length - 1; y > 0; y--) {
for (x = 0; x < grid[y].length; x++) {
if (grid[y][x] === chars.empty && grid[y - 1][x] === chars.block) {
grid[y][x] = chars.block;
grid[y - 1][x] = chars.empty;
}
}
}
}
// Just for the demo
var t = 0, loop = setInterval(function () {
draw();
if (grid[0].includes(chars.block)) {
clearInterval(loop);
grid[9] = 'GAME OVER!'.split('');
}
document.body.innerHTML = '<pre style="font-size:.6em">'
+ grid.map(row => row.join(' ')).join('\n')
+ '</pre>';
if (t % 20 === 0) {
grid[0][Math.floor(Math.random() * 10)] = chars.block;
}
t++;
}, 20);

Canvas coordinates and collision in JS(Snake)

Im coding Snake from JS via this tutorial: https://medium.com/free-code-camp/think-like-a-programmer-how-to-build-snake-using-only-javascript-html-and-css-7b1479c3339e
But Im not that familiar with Canvas.
In two functions, one for collision and one for creating the food there's the following code that I need explained:
function didGameEnd() {
for (let i = 4; i < snake.length; i++) {
if (snake[i].x === snake[0].x && snake[i].y === snake[0].y) return true
}
const hitLeftWall = snake[0].x < 0;
const hitRightWall = snake[0].x > gameCanvas.width - 10;
const hitToptWall = snake[0].y < 0;
const hitBottomWall = snake[0].y > gameCanvas.height - 10;
return hitLeftWall || hitRightWall || hitToptWall || hitBottomWall
}
In the function didGameEnd Im wondering what this stands for:
"< 0;" & "> gameCanvas.width - 10;" ? Im thinking the coordinates for the canvas but I can't really find a clear answer.
and also:
function createFood() {
foodX = randomTen(15, gameCanvas.width - 10);
foodY = randomTen(15, gameCanvas.height - 10);
generate a new food location
snake.forEach(function isFoodOnSnake(part) {
const foodIsoNsnake = part.x == foodX && part.y == foodY;
if (foodIsoNsnake) createFood();
});
}
And for createFood its also "gameCanvas.width - 10" and why it says 15 before ?

Why is `appendChild()` giving me issues?

H~ I have a script that I've been working on which will, ultimately, display Pascal's Triangle. I developed this from scratch with JavaScript, and I want to display it in the DOM.
For the life of me, I can't figure out why this script produces a different effect in the DOM than it does in a console. Any help would be much appreciated!
Copyright (c) 2015 Peter Gray Ward
function randomScripts(){
var arr1 = ['_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_'];
var arr2 = ['_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_'];
var n = 0;
var len = 35;
var mid = Math.floor(len/2)
var destination = document.getElementById("destination");
var home = document.getElementById("home");
function first(){
for(var j = 1; j<=10; j++){
var o = j%2 !== 0;
var e = j%2 === 0
if(o){
for(var i = 0; i<len; i++){
odd = i%2 !== 0;
if(j === 1){
arr1.splice(i,1,"_")
}
if(odd && (i === mid + n || i === mid - n)){
arr1.splice(i,1,1);
}
var node = document.createTextNode(arr1.join(''));
}
console.log(arr1.join(''));
destination.appendChild(node);
home.appendChild(node);
//destination.appendChild("Please see Console for full version b/c of hackers");
}
else if(e){
for(var h = 0; h<len; h++){
even = h%2 === 0;
if(even && (h === mid + n || h === mid - n)){
arr2.splice(h,1,n);
}
}
console.log(arr2.join(''));
}
n++;
}
}
<p id="destination"></p>
You created functions, but you never call them. Try actually calling them to see what they do:
function randomScripts(){
var arr1 = ['_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_'];
var arr2 = ['_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_'];
var n = 0;
var len = 35;
var mid = Math.floor(len/2)
var destination = document.getElementById("destination");
var home = document.getElementById("home");
first();
function first(){
for(var j = 1; j<=10; j++){
var o = j%2 !== 0;
var e = j%2 === 0
if(o){
for(var i = 0; i<len; i++){
odd = i%2 !== 0;
if(j === 1){
arr1.splice(i,1,"_")
}
if(odd && (i === mid + n || i === mid - n)){
arr1.splice(i,1,1);
}
var node = document.createElement('p');
node.textContent = arr1.join('');
}
console.log(arr1.join(''));
destination.appendChild(node);
home.appendChild(node);
destination.innerHTML = "Please see Console for full version b/c of hackers";
}
else if(e){
for(var h = 0; h<len; h++){
even = h%2 === 0;
if(even && (h === mid + n || h === mid - n)){
arr2.splice(h,1,n);
}
}
console.log(arr2.join(''));
}
n++;
}
}
}
window.addEventListener('load', randomScripts, false);
<p id="destination"></p>
<div id="home"></div>
There may be other problems in the script, but here are a couple I noticed. First:
destination.appendChild(node);
home.appendChild(node);
You can't append the same node in two different places in the DOM. It will remove the first one when you append it the second time. You could either create a second text node the same way you created the first, or clone the node.
Also, these two lines of code come after the if(o) statement body, but the node variable is created inside that if body. In the general case, you wouldn't expect this to work always, because node would be undefined if the if body were not executed. In your specific code, that won't happen because o is true the first time through the loop. But node only gets updated on every second pass through the loop. Is that what you want?
One last thing, the indentation is not consistent, making it hard to see what is nested inside what. It's important to use correct and consistent indentation. (And I would suggest 4 spaces instead of 2 to make it easier for people to read.)

Randomly generate objects in canvas without duplicate or overlap

How do I generate objects on a map, without them occupying the same space or overlapping on a HTML5 Canvas?
X coordinate is randomly generated, to an extent. I thought checking inside the array to see if it's there already, and the next 20 values after that (to account for the width), with no luck.
var nrOfPlatforms = 14,
platforms = [],
platformWidth = 20,
platformHeight = 20;
var generatePlatforms = function(){
var positiony = 0, type;
for (var i = 0; i < nrOfPlatforms; i++) {
type = ~~(Math.random()*5);
if (type == 0) type = 1;
else type = 0;
var positionx = (Math.random() * 4000) + 500 - (points/100);
var duplicatetest = 21;
for (var d = 0; d < duplicatetest; d++) {
var duplicate = $(jQuery.inArray((positionx + d), platforms));
if (duplicate > 0) {
var duplicateconfirmed = true;
}
}
if (duplicateconfirmed) {
var positionx = positionx + 20;
}
var duplicateconfirmed = false;
platforms[i] = new Platform(positionx,positiony,type);
}
}();
I originally made a cheat fix by having them generate in an area roughly 4000 big, decreasing the odds, but I want to increase the difficulty as the game progresses, by making them appear more together, to make it harder. But then they overlap.
In crude picture form, I want this
....[]....[].....[]..[]..[][]...
not this
......[]...[[]]...[[]]....[]....
I hope that makes sense.
For reference, here is the code before the array check and difficulty, just the cheap distance hack.
var nrOfPlatforms = 14,
platforms = [],
platformWidth = 20,
platformHeight = 20;
var generatePlatforms = function(){
var position = 0, type;
for (var i = 0; i < nrOfPlatforms; i++) {
type = ~~(Math.random()*5);
if (type == 0) type = 1;
else type = 0;
platforms[i] = new Platform((Math.random() * 4000) + 500,position,type);
}
}();
EDIT 1
after some debugging, duplicate is returning as [object Object] instead of the index number, not sure why though
EDIT 2
the problem is the objects are in the array platforms, and x is in the array object, so how can I search inside again ? , that's why it was failing before.
Thanks to firebug and console.log(platforms);
platforms = [Object { image=img, x=1128, y=260, more...}, Object { image=img, x=1640, y=260, more...} etc
You could implement a while loop that tries to insert an object and silently fails if it collides. Then add a counter and exit the while loop after a desired number of successful objects have been placed. If the objects are close together this loop might run longer so you might also want to give it a maximum life span. Or you could implement a 'is it even possible to place z objects on a map of x and y' to prevent it from running forever.
Here is an example of this (demo):
//Fill an array with 20x20 points at random locations without overlap
var platforms = [],
platformSize = 20,
platformWidth = 200,
platformHeight = 200;
function generatePlatforms(k) {
var placed = 0,
maxAttempts = k*10;
while(placed < k && maxAttempts > 0) {
var x = Math.floor(Math.random()*platformWidth),
y = Math.floor(Math.random()*platformHeight),
available = true;
for(var point in platforms) {
if(Math.abs(point.x-x) < platformSize && Math.abs(point.y-y) < platformSize) {
available = false;
break;
}
}
if(available) {
platforms.push({
x: x,
y: y
});
placed += 1;
}
maxAttempts -= 1;
}
}
generatePlatforms(14);
console.log(platforms);
Here's how you would implement a grid-snapped hash: http://jsfiddle.net/tqFuy/1/
var can = document.getElementById("can"),
ctx = can.getContext('2d'),
wid = can.width,
hei = can.height,
numPlatforms = 14,
platWid = 20,
platHei = 20,
platforms = [],
hash = {};
for(var i = 0; i < numPlatforms; i++){
// get x/y values snapped to platform width/height increments
var posX = Math.floor(Math.random()*(wid-platWid)/platWid)*platWid,
posY = Math.floor(Math.random()*(hei-platHei)/platHei)*platHei;
while (hash[posX + 'x' + posY]){
posX = Math.floor(Math.random()*wid/platWid)*platWid;
posY = Math.floor(Math.random()*hei/platHei)*platHei;
}
hash[posX + 'x' + posY] = 1;
platforms.push(new Platform(/* your arguments */));
}
Note that I'm concatenating the x and y values and using that as the hash key. This is to simplify the check, and is only a feasible solution because we are snapping the x/y coordinates to specific increments. The collision check would be more complicated if we weren't snapping.
For large sets (seems unlikely from your criteria), it'd probably be better to use an exclusion method: Generate an array of all possible positions, then for each "platform", pick an item from the array at random, then remove it from the array. This is similar to how you might go about shuffling a deck of cards.
Edit — One thing to note is that numPlatforms <= (wid*hei)/(platWid*platHei) must evaluate to true, otherwise the while loop will never end.
I found the answer on another question ( Searching for objects in JavaScript arrays ) using this bit of code to search the objects in the array
function search(array, value){
var j, k;
for (j = 0; j < array.length; j++) {
for (k in array[j]) {
if (array[j][k] === value) return j;
}
}
}
I also ended up rewriting a bunch of the code to speed it up elsewhere and recycle platforms better.
it works, but downside is I have fewer platforms, as it really starts to slow down. In the end this is what I wanted, but its no longer feasible to do it this way.
var platforms = new Array();
var nrOfPlatforms = 7;
platformWidth = 20,
platformHeight = 20;
var positionx = 0;
var positiony = 0;
var arrayneedle = 0;
var duplicatetest = 21;
function search(array, value){
var j, k;
for (j = 0; j < array.length; j++) {
for (k in array[j]) {
if (array[j][k] === value) return j;
}
}
}
function generatePlatforms(ind){
roughx = Math.round((Math.random() * 2000) + 500);
type = ~~(Math.random()*5);
if (type == 0) type = 1;
else type = 0;
var duplicate = false;
for (var d = 0; d < duplicatetest; d++) {
arrayneedle = roughx + d;
var result = search(platforms, arrayneedle);
if (result >= 0) {
duplicate = true;
}
}
if (duplicate = true) {
positionx = roughx + 20;
}
if (duplicate = false) {
positionx = roughx;
}
platforms[ind] = new Platform(positionx,positiony,type);
}
var generatedplatforms = function(){
for (var i = 0; i < nrOfPlatforms; i++) {
generatePlatforms(i);
};
}();
you go big data, generate all possibilities, store each in an array, shuffle the array,
trim the first X items, this is your non heuristic algorithm.

Categories