Recursive backtracking - Maze generator - javascript

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.

Related

How to extract elements of a matrix in a particular pattern using javascript?

How to extract elements of a matrix in a particular pattern using javascript?
following is the code for generating a 2d matrix of size any
var matrix = [];
for(var i=0; I<size; i++) {
matrix[i] = [];
for(var j=0; j<size; j++) {
matrix[i][j] = undefined;
}
}
so the first one is the original matrix. second one is the matrix made out by removing some elements/ cells.
// don't worry about splicing... this is just a demo.
[[3,5], [4,5], [4,2] ... ].forEach(([i,j], _, arr) => delete arr[i][j])
***** This is where you begin *****
(from the above splices matrix the following has to be done)
so what I want to achieve is that I want to separate or group cells from the above spliced matrix in the following manner. (like concentric circles/squares fashion by keeping in mind the fact that some cells are removed)
first you start at the central element [i,j], then move to the next level 3 x 3 & assign a value to all concentric cells (or separate cell address out)... then move to the next level 4 x 4 assign a value to all concentric cells (or separate cell address out) and so on...
Note that the spliced matrix is the starting point
travel in this fashion outwards (like concentric squares):
the idea is that:\
generate a matrix of size n * n.
remove some elements (that will be any)
... from here the algorithm starts...
separate cells from the above splices matrix in a concentric square fashion. --> this is where I want help (step 3 only)
In simple terms I want to separate out the cells from the matrix in the following order (one level at a time 3 x 3 first then 4 x 4 then 5 x 5 and so on.... ( from the above spliced matrix, which means that some cells will be already removed.)
keep in mind the fact that some cells are already removed (so you'll have to skip them -)
First, figure out the centroid of the square. The center is going to be at {length / 2, width / 2}. We're dealing in integer units, so floor. Using the convention that the top left of the figure is {0, 0} , then ie a 7x7 square has its center at
{floor(7/2), floor(7/2)} === {3,3}
Then, figure out a distance formula. In the pictures, the unit squares in purple are all the squares where either the x coordinate or y coordinate are N units away from the center. In other words
max(x - centerX, y-centerY) === N
I agree with #audzzy, you don't want to delete anything since that actually changes the length of an individual row, which we don't want. Instead just set it to another value.
So the idea is to:
find the center
iterate over each unit square
clear any unit squares which are N distance from the center
const clearCircle = (mat, N) => {
// find the center
const maxI = mat.length, maxJ = mat[0].length,
center = {
i: Math.floor(mat.length / 2),
j: Math.floor(mat[0].length / 2)
};
// iterate over all units
for (let i = 0; i < maxI; i++) {
for (let j = 0; j < maxJ; j++) {
// check if its N units from the center
if (Math.max(Math.abs(center.i - i), Math.abs(center.j - j)) === N) {
mat[i][j] = " ";
}
}
}
return mat;
}
Full example:
/*jshint esnext: true */
const generateMat = size => {
const mat = [];
for (let i = 0; i < size; i++) {
mat[i] = [];
for (let j = 0; j < size; j++) {
mat[i][j] = "[ ]";
}
}
return mat;
}
const mat = generateMat(9);
const logMat = mat => console.log("\n" + mat.map(row => (row).join(" ")).join("\n") + "\n");
const clearCircle = (mat, N) => {
// find the center
const maxI = mat.length, maxJ = mat[0].length,
center = {
i: Math.floor(mat.length / 2),
j: Math.floor(mat[0].length / 2)
};
// iterate over all units
for (let i = 0; i < maxI; i++) {
for (let j = 0; j < maxJ; j++) {
// check if its N units from the center
if (Math.max(Math.abs(center.i - i), Math.abs(center.j - j)) === N) {
mat[i][j] = " ";
}
}
}
return mat;
}
logMat(mat);
logMat(clearCircle(mat, 1));
logMat(clearCircle(mat, 2));
logMat(clearCircle(mat, 3));
Well... you just need exclude squares outside boundaries, after that you can just select elements which are in the most external layer of the matrix!
Something like that:
function extractElements(layer, matrix) {
const elements = []
const n = matrix.length; // matrix NxN
const frontIndex = layer -1;
const backIndex = n - layer;
if(layer <= 0 || layer > Math.ceil(n/2)) {
console.log("invalid layer");
return [];
}
for(let i = 0; i < n; ++i){
if(i < frontIndex || i > backIndex)
continue;
for(let j = 0; j < n; ++j){
if(j < frontIndex || j > backIndex)
continue;
if(i === frontIndex || i === backIndex || j === frontIndex || j === backIndex) {
if(matrix[i][j] !== undefined)
elements.push(matrix[i][j]);
}
}
}
return elements;
}
So, if you want the most external layer of a matrix 5x5, you can call extractElements(1, matrix)
Good code!
here's some basic code to do what you want (here I colored the cells, you could add them to a result array, or do whateve else you want)
notice it has very little iterations- only goes over the "wanted" cells every time- and colors the relevant lines and columns,
(for visual reasons -
'-' is a regular cell,
' ' is deleted cell,
'a' is a "purple" cell)
setMatrix is basically the interesting part that gets the interesting cells of every level..
let setMatrix = (matrix, level, value) => {
let size = matrix.length;
for(let x=Math.floor(size/2)-level;x<=Math.floor(size/2)+level;x++){
matrix[x][Math.floor(size/2)-level] = matrix[x][Math.floor(size/2)-level] == ' ' ? ' ' : value;
matrix[x][Math.floor(size/2)+level] = matrix[x][Math.floor(size/2)+level] == ' ' ? ' ' : value;
if(x!=Math.floor(size/2)-level && x!=Math.floor(size/2)+level){
matrix[Math.floor(size/2)-level][x] = matrix[Math.floor(size/2)-level][x] == ' ' ? ' ' : value;
matrix[Math.floor(size/2)+level][x] = matrix[Math.floor(size/2)+level][x] == ' ' ? ' ' : value;
}
}
}
and here's some code to call it for each level:
function doWork(){
// init
let size = 7;
var matrix = [];
for(var i=0; i<size; i++) {
matrix[i] = [];
for(var j=0; j<size; j++) {
matrix[i][j] = '-';
}
}
// delete
[[1,1], [2,3], [2,5], [5,1], [6,4]].forEach(([i,j])=> matrix[i][j] = ' ');
// go over each level
for(let i=0;i<=Math.floor(size/2);i++){
// set
setMatrix(matrix, i, 'a');
// print
for(let x=0;x<size;x++){
let line='';
for(let y=0;y<size;y++){
line+=matrix[x][y];
}
console.log(line);
}
console.log();
//reset
setMatrix(matrix, i, '-');
}
}
doWork();

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);

Picking up food object in radius

I'm making a Genetic Algorithm for a school project. Currently I'm building the foundation for the "dot" objects that move around and pick up food. I wrote some code that I thought should be able to do that and after some trial and error I came up with this. ( It's probably not the cleanest code and I would love to hear some tips about that as well). The only problem with the code is that the object does not just pick up 1 food but multiple and I don't know why.
I've tried putting the food objects index in a seperate Array so I can later remove it from the food Array (when I know the index to remove >> targetIndex)
checkForTarget() {
let inRange = new Array();
let indexArray = new Array();
for (let i = 0; i < food.length; i++) {
let d = dist(food[i].pos.x, food[i].pos.y, this.pos.x, this.pos.y);
if (d < this.sense) {
inRange.push(food[i]);
indexArray.push(i);
}
}
if (!inRange.length == 0) {
let closest = this.sense; // this.sense = radius
let target, targetIndex;
for (let i = 0; i < inRange.length; i++) {
let d = dist(inRange[i].pos.x, inRange[i].pos.y, this.pos.x, this.pos.y);
if (d < closest) {
target = inRange[i];
targetIndex = indexArray[i];
closest = d;
}
}
let targetpos = createVector(target.pos.x, target.pos.y); //fixed food removing from function (resetting position by using sub)
let desired = targetpos.sub(this.pos);
desired.normalize();
desired.mult(this.maxspeed);
let steeringForce = desired.sub(this.vel);
this.applyForce(steeringForce);
for (let i = 0; i < food.length; i++) {
let d = dist(target.pos.x, target.pos.y, this.pos.x, this.pos.y);
if (d < this.size) {
console.log(targetIndex);
this.food_eaten += 1;
food.splice(targetIndex, 1);
}
}
}
}
I don't get any error messages with this code, I used console.log to log the targetIndex. Which results in getting the same output multiple times.
[...] the object does not just pick up 1 food but multiple [...]
Of course it does, becaus in
for (let i = 0; i < food.length; i++) {
let d = dist(target.pos.x, target.pos.y, this.pos.x, this.pos.y);
if (d < this.size) {
// [...]
}
}
The condition d < this.size does not depend on food[i], if the condition is fulfilled it accepts each food in the array.
Just skip the for loop, to solve the issue. Note, you want to "eat" one food, so it is sufficient to verify if 1 food is in range. The index of the food is already identified and stored in targetIndex:
//for (let i = 0; i < food.length; i++) {
let d = dist(target.pos.x, target.pos.y, this.pos.x, this.pos.y);
if (d < this.size) {
console.log(targetIndex);
this.food_eaten += 1;
food.splice(targetIndex, 1);
}
//}

p5.js Prim's Algorithm Maze Generation: Stuck in Infinite Loop

Note: I also posted this question on p5.js' forum: https://discourse.processing.org/t/p5-js-prims-algorithm-maze-generation-stuck-in-infinite-loop/8277
I'm trying to implement a randomized Prim's algorithm to generate a maze. However, the program keeps getting stuck in an infinite loop as the length of the list of walls (wallList) is always in the thousands. Currently I am using an if statement that stops the maze generation after 11500 iterations to prevent an infinite loop.
My pseudocode is based on Wikipedia's description of the algorithm:
Start with a grid full of walls.
Pick a cell, mark it as part of the
maze. Add the walls of the cell to the wall list.
While there are
walls in the list:
Pick a random wall from the list. If only one of
the two cells that the wall divides is visited, then:
Make the wall a
passage and mark the unvisited cell as part of the maze.
Add the
neighboring walls of the cell to the wall list.
Remove the wall from
the list.
HTML:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.0/p5.js"></script>
</script>
</head>
<body>
<div id="game-wrapper">
<div id="canvas-wrapper">
</div>
</div>
</div>
<script type="text/javascript" src="maze.js"></script>
</body>
</html>
JavaScript (maze.js):
// Note to self: JavaScript variables have function scope, not block scope
var numIterations = 0; // Just for debugging purposes (stop at 1000 iterations otherwise the program goes into
// an infinite loop
// The directions and vectors arrays correspond to each other
// For example, the first element of directions is "N" and the first element of vectors also represents a
// north vector
var directions = ["N", "E", "S", "W"]
var vectors = [
[-1, 0],
[0, 1],
[1, 0],
[0, -1]
];
var wallList = {}; // Structure of a wall [rol (num), col (num), direction (string)]
function Maze(numRows, numColumns) {
/*
Defines a maze given the number of rows and the number of columns in the maze
*/
this.numColumns = numColumns;
this.numRows = numRows;
this.numCells = numRows * numColumns;
this.cellGraph = [];
for (var i = 0; i < numRows; i++) { // For every single row
this.cellGraph.push([]); // Start out with an empty row
}
}
Maze.prototype.computeFrontierWalls = function (cellRow, cellColumn) {
/*
The frontier walls of a cell is defined as all the walls of the adjacent cells
*/
/*
Coordinates of adjacent cells:
Up [cellRow - 1, cellColumn]
Down [cellRow + 1, cellColumn]
Right [cellRow, cellColumn + 1]
Left [cellRow, cellColumn - 1]
*/
var coordinates = [
[cellRow - 1, cellColumn],
[cellRow + 1, cellColumn],
[cellRow, cellColumn + 1],
[cellRow, cellColumn - 1]
];
var computedFrontier = []; // List of frontier cells
var originalCell = this.cellGraph[cellRow][cellColumn]; // We want to calculate the frontier of the original cell
for (var i = 0; i < coordinates.length; i++) {
// Get the coordinates of the adjacent cell
var coordinate = coordinates[i];
var row = coordinate[0];
var col = coordinate[1];
// See if a cell exists at that area
// If there is a cell that exists, add all of the walls of the cell to the computedFrontier array
if (row >= 0 && row < this.cellGraph.length && col >= 0 && col < this.cellGraph[0].length) {
var cell = this.cellGraph[parseInt(row)][parseInt(col)];
for (var j = 0; j < directions.length; j++) {
computedFrontier.push([cell.row, cell.column, directions[j]]);
}
}
}
return computedFrontier;
}
function Cell(cellSize, row, column) {
this.cellSize = cellSize; // The width and height of the cell
this.column = column;
this.row = row;
this.xPos = column * cellSize;
this.yPos = row * cellSize;
this.walls = [true, true, true, true]; // 0 = top, 1 = right, 2 = bottom, 3 = left
this.visited = false; // Whether the cell has been traversed or not
}
function getRandomPos(widthCells, heightCells) {
var row = Math.floor(Math.random() * heightCells); // Generate a random row
var column = Math.floor(Math.random() * widthCells); // Generate a random column
return [row, column];
}
var mazeIntro = function (p) {
var maze = new Maze(20, 35); // Generate a new maze with 20 rows and 35 columns
Maze.prototype.createMaze = function () { // Build an empty maze
for (var i = 0; i < this.numRows; i++) { // Iterate through every row
for (var j = 0; j < this.numColumns; j++) { // Iterate through every column
var cell = new Cell(20, i, j); // Create a new size at row i and column j with size 20
maze.cellGraph[i].push(cell); // Add the cell to the row
}
}
}
maze.createMaze(); // Build the maze
p.setup = function () {
var canvas = p.createCanvas(700, 400);
p.background(255, 255, 255);
p.smooth();
p.noLoop();
// Pick a cell, mark it as part of the maze. Add the walls of the cell to the wall list
var pos = getRandomPos(maze.cellGraph[0].length, maze.cellGraph.length);
;
var row = pos[0];
var column = pos[1];
maze.cellGraph[row][column].visited = true;
for (var k = 0; k < directions.length; k++) {
var key = row.toString() + column.toString() + directions[k].toString();
if (!wallList[key]) {
wallList[key] = [row, column, directions[k]];
}
}
}
Cell.prototype.display = function () {
/*
For each wall:
1. Check if it is on the border of the maze:
2. If it is on the border: don't draw the wall
3. If it isn't on the border: draw the wall
*/
p.stroke(0, 0, 0);
if (this.walls[0] && this.row != 0) { // Top
p.line(this.xPos, this.yPos, this.xPos + this.cellSize, this.yPos);
}
if (this.walls[1] && this.column != maze.widthCells - 1) { // Right
p.line(this.xPos + this.cellSize, this.yPos, this.xPos + this.cellSize, this.yPos + this.cellSize);
}
if (this.walls[2] && this.row != maze.heightCells - 1) { // Bottom
p.line(this.xPos + this.cellSize, this.yPos + this.cellSize, this.xPos, this.yPos + this.cellSize);
}
if (this.walls[3] && this.column != 0) { // Left
p.line(this.xPos, this.yPos + this.cellSize, this.xPos, this.yPos);
}
p.noStroke();
}
Cell.prototype.toString = function () {
/*
Mainly used for debugging purposes, converts the object into a string containing the row and the column of the cell
*/
return this.row + "|" + this.column;
}
function deleteWall(current, neighbor) {
/*
Deletes two walls separating two cells: current and neighbor
Calculates if neighbor is to the left, right, top, or bottom of cell
Removes the current's wall and the corresponding neighbor's wall
*/
var deltaX = current.column - neighbor.column;
var deltaY = current.row - neighbor.row;
if (deltaX == 1) { // Current is to the right of the neighbor
current.walls[3] = false;
neighbor.walls[1] = false;
}
if (deltaX == -1) { // Current is to the left of the neighbor
current.walls[1] = false;
neighbor.walls[3] = false;
}
if (deltaY == 1) { // Current is to the bottom of the neighbor
current.walls[0] = false;
neighbor.walls[2] = false;
}
if (deltaY == -1) { // Current is to the top of the neighbor
current.walls[2] = false;
neighbor.walls[0] = false;
}
}
function isWall(cellA, cellB) {
// Whether there's a wall or not depends on the orientation of the blocks
// If it's vertical, it has to be false between even numbers
// If it's horizontal, it has to be false between odd numbers
for (var j = 0; j < cellA.walls.length; j++) {
for (var k = 0; k < cellB.walls.length; k++) {
if (Math.abs(j - k) == 2 && !cellA.walls[j] && !cellB.walls[k]) {
var rA = cellA.row;
var cA = cellA.column;
var rB = cellB.row;
var cB = cellB.column
if ((rA - rB) == 1 && j == 0 || (rA - rB) == -1 && j == 2 || (cA - cB) == 1 && j == 3 || (cA - cB) == -1 && j == 1) {
return false;
}
}
}
}
return true;
}
function calculateCellDivision(wall) {
// Calculate the two cells that the wall divides
// For example:
// If the wall is [10, 11, "N"]
// The two cells that the wall divides are (10, 11) and (9, 11)
var row = wall[0];
var col = wall[1];
var cell1 = maze.cellGraph[row][col]; // Get the cell of the wall
// Get the corresponding vector based upon the direction of the wall
var vectorIndex = directions.indexOf(wall[2]);
// Add the vector to the position of cell1
var cell2Row = parseInt(cell1.row) + vectors[vectorIndex][0];
var cell2Column = parseInt(cell1.column) + vectors[vectorIndex][1];
if (cell2Row < 0 || cell2Row >= maze.cellGraph.length || cell2Column < 0 || cell2 >= maze.cellGraph[0].length) {
return -1;
}
var cell2 = maze.cellGraph[cell2Row][cell2Column]; // Get the corresponding cell
var cellsVisited = 0;
var unvisitedCell;
if (cell1.visited) {
cellsVisited += 1;
unvisitedCell = cell2;
}
if (!cell2) { // This means that the wall is a border wall
return -1;
}
if (cell2.visited) {
cellsVisited += 1;
unvisitedCell = cell1;
}
if (cellsVisited == 1) {
return [cell1, cell2, cellsVisited, unvisitedCell];
}
return -1;
}
function getCellWalls(row, col) {
// Gets a cell's walls
var cellWalls = [];
for (var i = 0; i < directions.length; i++) {
cellWalls.push(row + col + directions[i]);
}
return cellWalls;
}
p.draw = function () {
while (Object.keys(wallList).length > 0) { // While there are still walls in the list
console.log("Object.keys(wallList).length = " +
// Pick a random wall of the list
var wallListKeys = $.map(wallList, function (value, key) {
return key;
});
var randomKey = wallListKeys[Math.floor(Math.random() * wallListKeys.length)];
var randomWall = wallList[randomKey];
var components = calculateCellDivision(randomWall);
if (components != -1) {
var numVisited = components[2];
var cell1 = components[0];
var cell2 = components[1];
// If only one of the two cells that the wall divides is visited, then:
// 1. Make the wall a passage and mark the unvisited cell as part of the maze.
// 2. Add the neighboring walls of the cell to the wall list.
// Remove the wall from the list.
if (numVisited == 1) {
deleteWall(cell1, cell2);
var unvisitedCell = maze.cellGraph[components[3].row][components[3].column];
unvisitedCell.visited = true;
var unvisitedString = unvisitedCell.row + "|" + unvisitedCell.column;
// Add the neighboring walls of the cell to the wall list
// Format of the walls (by index):
// 0 = top, 1 = right, 2 = bottom, 3 = left
var computedFrontierWalls = maze.computeFrontierWalls(unvisitedCell.row, unvisitedCell.column);
for (var k = 0; k < computedFrontierWalls.length; k++) {
var computedWall = computedFrontierWalls[k];
var keyString = computedWall[0].toString() + computedWall[1].toString() + computedWall[2];
if (!wallList[keyString]) {
wallList[keyString] = computedWall;
}
}
// Delete randomKey from the list of walls, and then delete the same wall from the corresponding cell
delete wallList[randomKey];
// Calculate the corresponding cell
var direction = randomWall[2];
var directionIndex = directions.indexOf(direction);
var oppositeDirectionIndex = -1;
if (directionIndex == 0) {
oppositeDirectionIndex = 2;
}
if (directionIndex == 2) {
oppositeDirectionIndex = 0;
}
if (directionIndex == 1) {
oppositeDirectionIndex = 3;
}
if (directionIndex == 3) {
oppositeDirectionIndex = 1;
}
var vector = vectors[directionIndex];
var correspondingString = (randomWall[0] + vector[0]).toString() + (randomWall[1] + vector[1]).toString() + directions[oppositeDirectionIndex];
delete wallList[correspondingString];
}
}
numIterations += 1;
if (numIterations == 11500) { // Prevents infinite loop
break;
}
}
p.clear();
// Draw the maze
for (var i = 0; i < maze.cellGraph.length; i++) { // Iterate through row
for (var j = 0; j < maze.cellGraph[i].length; j++) { // Iterate through every column
maze.cellGraph[i][j].display(); // Display the cell
}
}
p.line(0, 400, 400, 400);
}
};
var myp5 = new p5(mazeIntro, "canvas-wrapper"); // Initialize the graphics engine for the canvas
The generation does work, as shown in the screenshot below. But I'm pretty sure my implementation is not correct as the wall list shouldn't contain thousands of walls.
According to the Wikipedia article, a wall must be removed once is picked randomly and analysed, regardless of the results of said analysis. In your code, the lines delete wallList[randomKey]; and delete wallList[correspondingString]; must be outside of the conditional for the wall to be eliminated from the list.
After deleting said lines, replace this:
numIterations += 1;
if (numIterations == 11500) { // Prevents infinite loop
break;
}
with this:
delete wallList[randomKey];
delete wallList[correspondingString];
and you're good to go. (Disclaimer: I tested it and it worked; but that's a hefty amount of code you have there, so I'm not sure if anything else breaks).
I wanted to try and make it more readable so I could understand better and came up with this. Hope it helps.
// 1. Start with a grid full of walls.
const _WALL = '█';
const _PATH = ' ';
const _COLS = 60;
const _ROWS = 60;
let maze = [];
for(let i = 0; i < _COLS; i++){
maze.push([]);
for(let j = 0; j < _ROWS; j++)
maze[i][j] = _WALL;
}
// 2. Pick a cell, mark it as part of the maze.
let cell = {x:Math.floor(Math.random()*_COLS), y:Math.floor(Math.random()*_ROWS)};
maze[cell.x][cell.y] = _PATH;
// 2.1 Add the walls of the cell to the wall list.
let walls = [];
if(cell.x+1 < _COLS) walls.push({x:cell.x+1, y:cell.y});
if(cell.x-1 >= 0) walls.push({x:cell.x-1, y:cell.y});
if(cell.y+1 < _ROWS) walls.push({x:cell.x, y:cell.y+1});
if(cell.y-1 >= 0) walls.push({x:cell.x, y:cell.y-1});
// 3. While there are walls in the list:
while(walls.length > 0){
// 3.1 Pick a random wall from the list.
let wallIndex = Math.floor(Math.random() * walls.length);
let wall = walls[wallIndex];
// 3.2 If only one of the two cells that the wall divides is visited, then:
let uc = []; // uc will be short for 'unvisited cell'
if(wall.x+1 < _COLS && maze[wall.x+1][wall.y] === _PATH) uc.push({x:wall.x-1, y:wall.y});
if(wall.x-1 >= 0 && maze[wall.x-1][wall.y] === _PATH) uc.push({x:wall.x+1, y:wall.y});
if(wall.y+1 < _ROWS && maze[wall.x][wall.y+1] === _PATH) uc.push({x:wall.x, y:wall.y-1});
if(wall.y-1 >= 0 && maze[wall.x][wall.y-1] === _PATH) uc.push({x:wall.x, y:wall.y+1});
if(uc.length === 1){
// 3.2.1 Make the wall a passage and mark the unvisited cell as part of the maze.
maze[wall.x][wall.y] = _PATH;
if(uc[0].x >=0 && uc[0].x <_COLS && uc[0].y >=0 && uc[0].y<_ROWS){
maze[uc[0].x][uc[0].y] = _PATH;
// 3.2.2 Add the neighboring walls of the cell to the wall list.
if(uc[0].x+1 < _COLS && maze[uc[0].x+1][uc[0].y] === _WALL) walls.push({x:uc[0].x+1, y:uc[0].y});
if(uc[0].x-1 >= 0 && maze[uc[0].x-1][uc[0].y] === _WALL) walls.push({x:uc[0].x-1, y:uc[0].y});
if(uc[0].y+1 < _ROWS && maze[uc[0].x][uc[0].y+1] === _WALL) walls.push({x:uc[0].x, y:uc[0].y+1});
if(uc[0].y-1 >= 0 && maze[uc[0].x][uc[0].y-1] === _WALL) walls.push({x:uc[0].x, y:uc[0].y-1});
}
}
// 3.3 Remove the wall from the list.
walls.splice(wallIndex, 1);
}
console.table(maze);
function setup(){
createCanvas(400, 400);
fill(0);
let widthUnit = width / _COLS;
let heightUnit = height / _ROWS;
for(let i = 0; i < _COLS; i++)
for(let j = 0; j < _ROWS; j++)
if(maze[i][j] === _WALL){
//rect(i*widthUnit, j*heightUnit, widthUnit, heightUnit);
if(i-1 >= 0 && i+1 < _COLS){
if(maze[i-1][j] === _WALL) line((i+0.5)*widthUnit, (j+0.5)*heightUnit, i*widthUnit, (j+0.5)*heightUnit);
if(maze[i+1][j] === _WALL) line((i+0.5)*widthUnit, (j+0.5)*heightUnit, (i+1)*widthUnit, (j+0.5)*heightUnit);
}
if(j-1 >= 0 && j+1 < _ROWS){
if(maze[i][j-1] === _WALL) line((i+0.5)*widthUnit, (j+0.5)*heightUnit, (i+0.5)*widthUnit, j*heightUnit);
if(maze[i][j+1] === _WALL) line((i+0.5)*widthUnit, (j+0.5)*heightUnit, (i+0.5)*widthUnit, (j+1)*heightUnit);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>

Collision Script in javascript help needed

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.

Categories