I am trying to implement Conway's game of life using checkboxes. In theory I should start with some checkbox checked randomly (and possibly some other manually checked by the user) and then go to the next generation by hitting a button. The code I made so far starts with just unchecked checkboxes (the random part is a detail I will care about later).
Here's my code:
function createBoard( rows = 30, columns = 50 ) {
let board = document.createElement('div');
document.body.appendChild(board);
for(let i = 0; i < rows; i++) {
let row = document.createElement('div');
for(let j = 0; j < columns; j++) {
let checkbox = document.createElement('input');
checkbox.type = "checkbox";
checkbox.className = "cb";
row.appendChild(checkbox);
}
board.appendChild(row);
}
}
function nextGen( rows = 30, columns = 50 ) {
function neighbourhood( r, c ) {
let sum = 0;
for( let i = -1; i < 2; i++ ) {
for( let j = -1; j < 2; j++ ) {
if( r + i >= 0 && c + j >= 0 && r + i < rows && c + j < columns && (i || j) ) {
sum += current[r+i][c+j];
}
}
}
return sum;
}
let checkboxes = document.querySelectorAll('.cb');
const flatBoard = Array.prototype.map.call(checkboxes, cb => cb.checked);
let current = flatBoard.reduce((cur, cb, i) => (!(i%columns) ? cur.push([cb]) : cur[cur.length-1].push(cb)) && cur, []);
let counter = 0;
console.log("ONE");
for(let i = 0; i < rows; i++) {
for(let j = 0; j < columns; j++) {
console.log("TWO");
const sum = neighbourhood(i,j);
checkboxes[counter++].checked = sum == 3 || current[i][j] && sum == 2;
}
}
}
createBoard();
let nextButton = document.getElementById('next');
nextButton.addEventListener('click', nextGen, false);
<button id="next">Next</button>
Does anyone know why when you hit the button, the first console.log() is being executed while the second is not?
nextGen is called with an argument that is determined by the DOM API: the event object. This will be the first argument and set the parameter variable rows. This means the for condition on rows will be false and so you have no iterations of the outer for loop.
To fix this particular problem, make sure that nextGen is called without any arguments:
nextButton.addEventListener('click', () => nextGen(), false);
Related
I'm trying to create a system of many objects that preform an action when they collide with each other, I'm using the P5.min.js library.
I've set up an array for the grid and an array for the objects, but I can't figure out the right way to go through each grid cell and check only the objects inside that cell before moving on to the next cell.
Here's what I've got so far
let molecules = [];
const numOfMolecules = 100;
let collisions = 0;
let check = 0;
let maxR = 10; //max molecule radius
let minR = 2; //min molecule radius
let numOfCol = 5;
let numOfRow = 5;
let CellW = 600/numOfCol; //gridWidth
let CellH = 600/numOfRow; //gridHeight
let remain = numOfMolecules;
let gridArray = [];
function setup() {
createCanvas(600, 600);
background(127);
for (let i = 0; i < numOfMolecules; i++) {
molecules.push(new Molecule());
}
}
function draw() {
background(127);
molecules.forEach(molecule => {
molecule.render();
molecule.checkEdges();
molecule.step();
});
drawGrid();
splitIntoGrid();
collision();
displayFR();
}
function drawGrid() {
for (i = 0; i < numOfRow+1; i++){
for (j = 0; j < numOfCol+1; j++){
noFill();
stroke(0);
rect(CellW*(j-1), CellH*(i-1), CellW, CellH);
}
}
}
function splitIntoGrid(){
for (let i = 0; i < numOfRow; i++){
for (let j = 0; j < numOfCol; j++){
tempArray = [];
molecules.forEach(molecule => {
if (molecule.position.x > (CellW*j) &&
molecule.position.x < (CellW*(j+1)) &&
molecule.position.y > (CellH*i) &&
molecule.position.y < (CellH*(i+1))) {
tempArray.push(molecule.id);
}
});
}
}
}
How I'm checking collision between all objects:
function collision() {
for (let i=0; i < molecules.length; i++){
for (let j=0; j < molecules.length; j++){
let diff = p5.Vector.sub(molecules[j].position, molecules[i].position);
check++;
if (i != j && diff.mag() <= molecules[j].radius + molecules[i].radius){
collisions++;
molecules[j].changeColor();
}
}
}
}
As far as I can see, I need to put these for loops inside another one going through each cell in the grid, but I don't know how to limit the search to which ever tempArray(s) the object is in
If this makes any sense, this is what I'm trying to do
function collision() {
for (let k = 0; k < gridArray.length; k++){
for (let i=0; i < gridArray.tempArray.length; i++){
for (let j=0; j < gridArray.tempArray.length; j++){
let diff = p5.Vector.sub(gridArray.tempArray[j].position, gridArray.tempArray.position);
check++;
if (i != j && diff.mag() <= gridArray.tempArray[j].radius + gridArray.tempArray[i].radius){
collisions++;
gridArray.tempArray[j].changeColor();
gridArray.tempArray[i].changeColor();
}
}
}
}
}
The grid cell is represented by an array of array gridArray. You need to have a collection of molecules for each grid cell. My recommendation would be to use Sets instead of an Array since the order is irrelevant. The idea is to be able to access the set of molecules on a given grid cell (i,j) with the syntax:
gridArray[i][j]
The following code will create an array of numOfRow arrays:
const numOfRow = 5;
const gridArray = (new Array(numOfRow)).fill([]);
gridArray with look like this:
[ [], [], [], [], [] ]
Inside splitIntoGrid you are checking which molecules are in which grid cells. This is good. However, for each grid cell, you are overwriting the global variable tempArray. Therefore, at the end of the function's execution, tempArray will only hold the molecules of the last grid cell, which isn't what you want. For a given grid cell, we will add the right molecules to a Set associated with this grid cell.
The Set data structure has an #add method which appends a new element to the set:
function splitIntoGrid() {
for (let i = 0; i < numOfRow; i++) {
for (let j = 0; j < numOfCol; j++) {
gridArray[i][j] = new Set();
molecules.forEach(molecule => {
if (molecule.position.x > (CellW*j)
&& molecule.position.x < (CellW*(j+1))
&& molecule.position.y > (CellH*i)
&& molecule.position.y < (CellH*(i+1))) {
gridArray[i][j].add(molecule);
}
});
}
}
}
Now you're ready to check for collisions on each grid cells. We will have a total of four loops inside one another. Two to navigate through the grid and two to compare the molecules that are contained inside each grid cell:
function collision() {
for (let i = 0; i < numOfRow; i++) {
for (let j = 0; j < numOfCol; j++) {
gridArray[i][j].forEach(moleculeA => {
gridArray[i][j].forEach(moleculeB => {
const diff = p5.Vector.sub(moleculeA.position, moleculeB.position);
if (moleculeA != moleculeB && diff.mag() <= moleculeA.radius + moleculeB.radius) {
collisions++;
moleculeA.changeColor();
moleculeB.changeColor();
}
});
});
}
}
}
In the above code, #forEach comes in handy.
I am considerably new to JavaScript. I have an 8 x 8 HTML table as a board for a game that is dynamically created in the JS file like this:
function drawBoard(rows, cols){
var grid = document.getElementById("grid");
grid.className = 'grid';
for (var r = 0; r < rows; r++){
var row = grid.appendChild(document.createElement('tr'));
for (var c = 0; c < cols; c++){
var cell = row.appendChild(document.createElement('td'));
}
}
return grid;
}
document.body.appendChild(drawBoard(ROW, COL));
The board displays successfully when I load the page. I need to insert 5 keywords randomly on 5 cells. These cells cannot be along the outside border of squares (i.e., cannot be in the first or last row or column). I have the 5 keywords as an array in my javascript file. How can I randomly pick 5 cells on the table excluding the first or last row or column and insert these keywords on them everytime I draw the board? I tried cell.innerHTML = "keyword" but ended up inserting only one of the keywords on every single cell on the table. Did a thorough search online without any luck. Thank you!
// I'm assuming ROW, COL and keywords are declared somewhere in your code
const ROW = 8;
const COL = 8;
const keywords = ['a', 'b', 'c', 'd', 'e'];
// --- your code, no changes
function drawBoard(rows, cols, keywords) {
var grid = document.getElementById("grid");
grid.className = 'grid';
for (var r = 0; r < rows; r++) {
var row = grid.appendChild(document.createElement('tr'));
for (var c = 0; c < cols; c++) {
row.appendChild(document.createElement('td'));
}
}
return grid;
}
// save the table in a variable
const grd = document.body.appendChild(drawBoard(ROW, COL));
//--- NEW CODE STARTS HERE
const shuffle = array => {
const shuffled = [];
while (array.length) {
const index = Math.floor(Math.random() * array.length);
shuffled.push(array.splice(index, 1).pop());
}
return shuffled;
};
const filtered = Array.from(grd.querySelectorAll('td'))
.filter(({cellIndex, parentElement: {rowIndex}}) => rowIndex && rowIndex < ROW - 1 && cellIndex && cellIndex < COL - 1);
const shuffled = shuffle(filtered);
const sliced = filtered.slice(0, keywords.length);
sliced.forEach((cell, index) => cell.innerHTML = keywords[index]);
<table id="grid"></table>
filtered is all the cells not on the border
shuffled array is simply all the filtered cells shuffled using the shuffle function
sliced array is the first n cells from the shuffled array, where n is the number of keywords
then it's a simple matter of adding the keywords
To explain the filter function:
({cellIndex, parentElement: {rowIndex}}) =>
rowIndex && rowIndex < ROW - 1 && cellIndex && cellIndex < COL - 1;
this is ES6 way of performing
function(cell) {
var cellIndex = cell.cellIndex;
var parentElement = cell.parentElement;
var rowIndex = parentElement.rowIndex;
return rowIndex && rowIndex < ROW - 1 && cellIndex && cellIndex < COL - 1;
}
First of all extract 5 random cells in the 64 - 28 available. We have a 6x6 square available then.
//We keep an array to store cells for keywords
var keyCells = [];
//A function to check if we already choosed a cell
function checkChoosed( x, y ) {
if( keyCells.length > 0 ) {
for( var j = 0; j < keyCells.length; j++ ) {
if( ( keyCells[ j ].x == x ) && ( keyCells[ j ].y == y ) ) return 1;
}
}
return 0;
}
//Pick random coordinates (in 6x6 square) for cells
function randomCells() {
var choosed = 0;
while( choosed < 5 ) {
var x = Math.round( Math.random() * 5 ) + 1; //Math.random returns a number between 0 and 0.99, so * 5 to get from 0 to 4.95, + 1 because we skip first row and column of the grid
var y = Math.round( Math.random() * 5 ) + 1;
if( !checkChoosed( x, y ) ) {
keyCells.push( { x: x, y: y } );
choosed++;
}
}
}
//Now we draw the table and access it for keywords
function drawBoard(rows, cols) {
var grid = document.getElementById("grid");
grid.className = 'grid';
for ( var r = 0; r < rows; r++ ) {
var row = grid.appendChild(document.createElement('tr'));
for ( var c = 0; c < cols; c++ ) {
var cell = row.appendChild(document.createElement('td'));
}
}
randomCells();
var trs = document.getElementById('grid').children;
for( var i = 0; i < keyCells.length; i++ ) {
var currX = keyCells[ i ].x;
var currY = keyCells[ i ].y;
//Here I used the ":nth-child" CSS selector to access "tr" (our Y) and then "td" (our X)
var currRow = trs[ currY ].children;
currRow[ currX ].innerHTML = /* your keyword here */
}
return grid;
}
Ok so i tried to make conway's game of life in p5.js and i am stuck in some wierd bug .
function make2DArray(cols, rows) {
let arr = new Array(cols);
for (let i = 0; i < arr.length; i++) {
arr[i] = new Array(rows);
}
return arr;
}
function countNeighbors(grid, x, y) {
let sum = 0;
for (let i = -1; i < 2; i++) {
for (let j = -1; j < 2; j++) {
let col = x + i;
let row = y + i;
if (grid[col][row] === undefined){
sum += grid[col][row];
}
}
}
sum-=grid[x][y]
return sum;
}
let grid;
let next;
let cols;
let rows;
let resolution = 20;
let fr = 15;
function setup() {
createCanvas(600, 400);
frameRate(fr);
cols = width / resolution;
rows = height / resolution;
next = make2DArray(cols, rows);
grid = make2DArray(cols, rows);
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
grid[i][j] = floor(random(2));
}
}
}
function draw() {
background(0);
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
let x = i * resolution;
let y = j * resolution;
if (grid[i][j] == 1) {
fill(0);
stroke(150);
rect(x, y, resolution - 1, resolution - 1);
} else {
fill(255);
stroke(150);
rect(x, y, resolution - 1, resolution - 1);
}
}
}
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
let state = grid[i][j];
let neighbors = countNeighbors(grid, i, j);
if (state == 0 && neighbors == 3) {
next[i][j] = 1;
} else if (state == 1 && (neighbors < 2 || neighbors > 3)) {
next[i][j] = 0;
} else {
next[i][j] = state;
}
}
}
grid = next;
}
Soo basically i have function countNeighbors and i think that bug is in there , i tried checking if col and row are i boundaries of array
like this
if (col>-1 && col <grid.length && row>-1 && row < grid[0].length){
//increase sum
}
but its even worse. I am new to js so i figured out that if for example
let x=new Array(10);
//and then when i try this
console.log(c[-1])
//it should give undefined
but program still wont work :(
Thanks!
With a few changes you can make it work:
1) Replace let row = y + i; by let row = y + j; That typo was making most of the counts off.
2) Replace the condition
if (grid[col][row] === undefined){
by
if (0 <= col && col < cols && 0 <= row && row < rows){
The problem with your original condition is that if grid[col] is undefined then grid[col][row] is undefined[row], which doesn't make sense.
3) For consistency sake, add a semicolon to the end of sum-=grid[x][y]
4) Finally, so as to not create an unintended alias, you need to replace grid = next by something like:
for(let i = 0; i < grid.length; i++){
grid[i] = next[i].slice();
}
Alternatively, make next a variable which is local to draw() and place the line let next = make2DArray(cols, rows); to the beginning of draw().
Kindly help me investigate my function below since I'm stuck and still having a hard time figuring it out.
All is well until it reaches the last column on the nested FOR loop. The last column of each row's values are only "0". However, I used the Number() function to make the cell values(i.e. "0") a number but I keep on getting NaN for the last element of the SUM & COUNT arrays.
colCount = 326 while rowCount = 374.
sum.length and count.length should really be ONLY 325 since the headers are unnecessary and the first column is just composed of time stamps. I was able to .push(0) successfully until the nested FOR loop changed the result of the last element to NaN.
function processDataToDictionary(csv) {
var allTextLines = csv.split(/\r\n|\n/);
var csvArray = [];
for (let i = 0; i < allTextLines.length - 1; i++) {
var row = allTextLines[i].split(',');
csvArray.push(row);
}
var colCount = csvArray[0].length;
var rowCount = csvArray.length;
//Arrays of values
var count = [];
var sum = [];
var average = [];
var headers = [];
for (let i = 1; i < colCount; i++) {
var current = csvArray[0][i].replace(/"/g, '');
sum.push(0);
count.push(0);
headers[i] = current;
}
for (let i = 1; i < rowCount; i++) {
for (let j = 1; j < colCount; j++) {
// Remove the quotes from your array
current = csvArray[i][j].replace(/"/g, '');
// Added the Method IsNullOrWhiteSpace
if (!isNullOrWhitespace(current)) {
// Parse as double not int to account for dec. values
sum[j] += Number(current);
count[j]++;
}
}
}
for (let i = 0; i < colCount; i++) {
average.push((sum[i] + 0.0) / count[i]);
}
for (let i = 1; i < colCount; i++) {
// create an empty array
dictionary[headers[i]] = average[i];
}
return dictionary;
}
function isNullOrWhitespace(input) {
if (input == " ") {
return true;
} else {
return false;
}
}
This gives you a dictionary (Object) with the columns names as keys and numbers that appear to be the correct averages as values. But one must still check whether there is a fault in the logic somewhere and the averages are not correct in fact.
function processDataToDictionary(csv) {
function isNullOrWhitespace(input) {
if (input === " ") {
return true;
} else if (input === null) {
return true;
//} else if (input === undefined) {
//return true;
} else {
return false;
}
}
var allTextLines = csv.split(/\r\n|\n/);
var csvArray = [];
for (let i = 0; i < allTextLines.length - 1; i++) {
var row = allTextLines[i].split(',');
csvArray.push(row);
}
var colCount = csvArray[0].length;
var rowCount = csvArray.length;
//Arrays of values
var count = [];
var sum = [];
var average = [];
var headers = [];
for (let i = 1; i < colCount; i++) {
var current = csvArray[0][i].replace(/"/g, '');
sum.push(0);
count.push(0);
headers[i] = current;
}
/**** I added these two lines ****/
sum.push(0);
count.push(0);
for (let i = 1; i < rowCount; i++) {
for (let j = 1; j < colCount ; j++) {
// Remove the quotes from your array
current = csvArray[i][j].replace(/"/g, '');
// Added the Method IsNullOrWhiteSpace
if (!isNullOrWhitespace(current)) {
// Parse as double not int to account for dec. values
sum[j] += Number(current);
count[j]++;
}
}
}
for (let i = 0; i < colCount; i++) {
average.push((sum[i] + 0.0) / count[i]);
}
// I added this line:
dictionary = {};
for (let i = 1; i < colCount; i++) {
dictionary[headers[i]] = average[i];
}
return dictionary;
}
Let me know if this works out for you. You can loop through the values with: for (let key in dictionary) {console.log("key: " + key + " , value: " + dictionary[key]);} . Regards!
I need help fixing my existing code to accomplish what I am trying to do.
with the following sample data:
var SAMPLE_DATA = [{start: 30, end: 150}, {start: 540, end: 600}, {start: 560, end: 620}, {start: 610, end: 670}];
I need to do the following:
iterate through each sample object
determine if the current objects range (obj.start:obj.end) overlaps with any other object ranges.
record the total number of overlaps for that object into totalSlots property
determine the "index" of the object (used for it's left-to-right positioning)
mockup of what I am trying to accomplish:
As you can see in the mockup, slotIndex is used to determine the left-to-right ordering of the display. totalSlots is how many objects it shares space with (1 meaning it is the only object). 100 / totalSlots tells me how wide the square can be (i.e. totalSlots=2, means it is 100 / 2, or 50% container width).
Current Output from my code
Obj[0] slotIndex=0, totalSlots=0
Obj[1] slotIndex=1, totalSlots=1
Obj[2] slotIndex=1, totalSlots=2
Obj[3] slotIndex=0, totalSlots=1
expected/desired output from my code:
Obj[0] slotIndex=0, totalSlots=0
Obj[1] slotIndex=0, totalSlots=1
Obj[2] slotIndex=1, totalSlots=2
Obj[3] slotIndex=0, totalSlots=1
the code:
detectSlots: function(oldEventArr) {
oldEventArr.sort(this.eventSorter);
var newEventArr = [],
n = oldEventArr.length;
for (var i = 0; i < n; i++) {
var currObj = oldEventArr[i];
if ('undefined' == typeof currObj.totalSlots) {
currObj.slotIndex = 0;
currObj.totalSlots = 0;
}
for (var x = 0; x < n; x++) {
if (i == x) {
continue;
}
var nextObj = oldEventArr[x];
if (currObj.start <= nextObj.end && nextObj.start <= currObj.end) {
currObj.totalSlots++;
nextObj.slotIndex++;
}
}
newEventArr.push(currObj);
}
return newEventArr;
}
Please help me figure out what is going wrong in my code. I'm about 90% sure the problem lies in the if(currObj.start <= nextObj.end && nextObj.start <= currObj.end) statement where I am assigning/incrementing the values but I could use an extra set of eyes on this.
The slotIndex value can be calculated by using graph colouring algorithm. Note that brute force algorithm is exponential in time and will only be a viable solution for a small set of overlapping slots. Other algorithms are heuristics and you won't be guaranteed the least slot possible.
Here is an example of heuristic for your problem:
...
// init
var newEventArr = [], n = oldEventArr.length;
for (var i = 0; i < n; i+=1) {
var currObj = oldEventArr[i];
newEventArr.push({"start":currObj.start,"end":currObj.end,"slotIndex":undefined,"totalSlots":0});
}
var link = {};
// create link lists and totals
for (var i = 0; i < n; i+=1) {
var currObj = newEventArr[i];
if (!link.hasOwnProperty(""+i))
link[""+i] = {};
for (var j = i+1; j < n; j+=1) {
var nextObj = newEventArr[j];
var not_overlap = (currObj.end <= nextObj.start || nextObj.end <= currObj.start);
if (!not_overlap) {
currObj.totalSlots+=1;
nextObj.totalSlots+=1;
link[""+i][""+j] = 1;
if (!link.hasOwnProperty(""+j))
link[""+j] = {};
link[""+j][""+i] = 1;
}
}
}
var arrities = [];
for (var i = 0; i < n; i+=1) {
arrities.push( {"arrity":newEventArr[i].totalSlots, "indx":i} );
}
// sort by arrities [a better solution is using a priority queue]
for (var i = 0; i < n-1; i+=1) {
var current_arrity = -1, indx = -1;
for (var j = i; j < n; j+=1) {
if (arrities[j].arrity > current_arrity) {
indx = j;
current_arrity = arrities[j].arrity;
}
}
var temp = arrities[i];
arrities[i] = arrities[indx];
arrities[indx] = temp;
}
for (var i = 0; i < n; i+=1) {
var nodeIndex = arrities[i].indx;
// init used colors
var colors = [];
for (var j = 0; j < n; j+=1) {
colors.push(0);
}
//find used colors on links
for (var k in link[""+nodeIndex]) {
var color = newEventArr[k].slotIndex;
if (color || color === 0)
colors[color] += 1;
}
//find the first unused color
for (var j = 0; j < n; j+=1) {
if (colors[j] <= 0) {
// color the node
newEventArr[nodeIndex].slotIndex = j;
break;
}
}
}
return newEventArr;
...
like this
var not_overlap = (currObj.end <= nextObj.start || nextObj.end <= currObj.start);
if (!not_overlap) { ...
or
var overlap = (currObj.end > nextObj.start && nextObj.end < currObj.start);
if (overlap) { ...