I'm currently working on a little jquery project. I want to build Conway's game of life with javascript/jquery/html. But I can't figure out, how to detect if a cell has alive neighbours. But I know I have to make use of Arrays.
Here what I came up with so far:
$(document).ready(function () {
var $create_grid = $('#create_grid');
var $run = $('#run');
var $reset = $('#reset');
var $random = $('#random');
var $cells = {};
var $active_cells = {};
$create_grid.click(function () {
var width = $("[name='width']").val();
var height = $("[name='height']").val();
var cellsize = $("[name='cellsize']").val();
var $table = $('#game');
if (width.length != 0 && height.length != 0 && cellsize.length != 0) {
for (i = 1; i <= height; i++) {
$('table').append('<tr id="' + i + '"></tr>');
}
for (i = 1; i <= width; i++) {
$('table tr').append('<td class="test" id="' + i + '"></td>');
}
$cells = $('table#game td');
$cells.css('width', cellsize);
$cells.css('height', cellsize);
} else { alert("Please fill out all the fields!"); }
$create_grid.hide('fast');
$('ul.parameters').hide('fast');
$random.css('display', 'block');
$reset.css('display', 'block');
//RESET CELLS
$reset.click(function () {
$cells.removeClass('alive');
});
//DRAW CELLS
var isDown = false;
$cells.mousedown(function () {
isDown = true;
})
.mouseup(function () {
isDown = false;
});
$cells.mouseover(function () {
if (isDown) {
$(this).toggleClass('alive');
}
});
$cells.click(function () {
$(this).toggleClass('alive');
});
});
//RANDOM PATTERN
function shuffle(array) {
var m = array.length, t, i;
// While there remain elements to shuffle…
while (m) {
// Pick a remaining element…
i = Math.floor(Math.random() * m--);
// And swap it with the current element.
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
}
$random.click(function () {
$(shuffle($cells).slice(0, 30)).addClass("alive");
});
//RUN SIMULATION
$run.click(function simulate() {
//GET NEIGHBOUR CELLS
$cells_alive = $('#game td.alive').length;
for (var c = 1; c <= $cells_alive; c++) {
alert(c);
};
});
});
Your assigned ids are not unique. All rows and all columns have ids from 1..n respectively 1..m. So every number from 1..min(n,m) is used twice. You should change this.
You might also want to assign some classes or data-attributes or just anything which makes it possible to actually select any of the html elements you create.
This for example sets some data-attributes to all tr and td tags.
for (i = 1; i <= height; i++) {
var elem = $('<tr></tr>');
elem.data('column', i);
$('table').append(elem);
}
for (i = 1; i <= width; i++) {
var elem = $('<td></td>');
elem.data('row', i);
$('table tr').append();
}
$cells = $('table#game td');
$cells.css('width', cellsize);
$cells.css('height', cellsize);
If you have coordinates (x, y) you may select all neighbors like
$('tr[data-column='+(x-1)+'] td[data-row='+y+'],
tr[data-column='+(x+1)+'] td[data-row='+y+'],
tr[data-column='+x+'] td[data-row='+(y-1)+'],
tr[data-column='+x+'] td[data-row='+(y+1)+']');
(You might want to consider class instead for efficiency reasons. Although I do not know if this makes a notable difference.)
EDIT:
Here's a question about the performance of data- vs class selectors: Are data attribute css selectors faster than class selectors?
Related
I am trying to replicate a mosaic that can be found on many webpages. While looking for solutions to my problem I came accross a very good implementation on squarespace seen at https://native-demo.squarespace.com/images-native/. I have hosted my website containing the mosaic at http://alexstiles.000webhostapp.com.
My issue is that when you resize the window the horizontal spacing between the images flucuates like it has an animation instead of staying consistient. Although it sorts itself out if you resize slowly, rapid movements can cause the margin to be to large or small. This behavior is not present in the squarespace template. How can I remove this and make the margin consistient when resizing? I have already tried using css margins instead of javascript. I have added the javascript below to help.
let mosaic = document.getElementsByClassName("mosaic")[0]; // For a single mosaic for now
let numImages = 12;
let imageTopic = "design";
let originalImageTopic = "design";
let rowWidth = 3;
let scale = 1;
let mosaicLoader = document.getElementsByClassName("loader-container")[0]
let search = document.getElementById("search");
let searchBtn = document.getElementById("search-button");
let form = document.getElementsByTagName("form")[0];
function rem(rems) {
return rems * (16 * scale);
}
searchBtn.addEventListener("click", function(event) {
event.preventDefault();
searchResult(search.value);
});
search.addEventListener("click", function() {
form.classList.add("focused");
});
search.addEventListener("blur", function() {
form.classList.remove("focused");
});
document.onkeydown = checkKey;
function checkKey(e) {
e = e || window.event;
if (e.keyCode == '13' || e.which == '13' || e.key === "Enter") {
e.preventDefault();
searchResult(search.value);
}
}
if (window.location.hash) {
let hash = window.location.hash.substring(1); //Puts hash in variable, and removes the # character
search.value = hash;
searchResult(hash);
}
function searchResult(query) {
// Get and set query
query = ((query != "" && query != originalImageTopic) ? search.value : imageTopic);
imageTopic = query;
if (imageTopic != originalImageTopic) {
parent.location.hash = imageTopic;
document.getElementById("search-term").textContent = imageTopic + " pictures";
}
// Put up loader while fetching images
mosaicLoader.style.display = "flex";
// Remove old images
let length = mosaic.children.length;
while (mosaic.lastChild && length > 1) {
mosaic.removeChild(mosaic.lastChild);
length--;
}
// Create new images
let loadedImageNum = 0
for (let i = 0; i < numImages; i++) {
let image = document.createElement("img");
image.src = `https://source.unsplash.com/random?${imageTopic}/sig${i}/`;
mosaic.appendChild(image);
}
// Wait for all images to load
let loadedImages = 0;
let image = mosaic.querySelectorAll("img");
imageCheck = setInterval(function() {
for (let i = 0; i < numImages; i++) {
if (image[i].naturalHeight !== 0 && image[i].complete) {
loadedImages++;
}
if (loadedImages == numImages) {
clearInterval(imageCheck);
// alert("Loaded!")
// Lay them out
setTimeout(imagesLoaded, 2000); // Needs some time before laying out
// Break loop
break;
}
}
}, 200)
}
searchResult(imageTopic); // Inital images with no search query
window.onload = function() {
windowResizeEvents();
}
window.onresize = function() {
windowResizeEvents();
}
function windowResizeEvents() {
imagesLoaded();
}
function mosaicCalibration() {
let images = document.querySelectorAll(".mosaic img");
if (window.innerWidth < 750) {
mosaic.classList.add("column");
} else if (window.innerWidth < 1100) {
rowWidth = 2;
} else {
rowWidth = 3 // Math.round(window.innerWidth/(426 + (2/3)));
}
if (window.innerWidth > 750) {
mosaic.classList.remove("column");
for (let i = 0; i < images.length; i++) {
images[i].style.width = `calc((100% - ${((rowWidth - 1) * 1)}rem) / ${rowWidth})`;
}
}
}
function imagesLoaded() {
// Find out row width, image width, etc
mosaicCalibration();
// Remove loader and set height to 0 so new height can be calculated
mosaicLoader.style.display = "none";
mosaic.style.height = 0;
// Define variables
let images = document.querySelectorAll(".mosaic img");
let row = 0;
let rowNum = 0;
let margin = rem(1.25);
let imageWidth = ((mosaic.scrollWidth - (2 * margin)) / rowWidth);
let column = [];
for (let i = 0; i < images.length; i++) {
images[i].style.top = i > rowWidth - 1 ? column[i-rowWidth] + margin : 0;
if (row < rowWidth) {
if (window.innerWidth > 1100) {
images[i].style.left = row * imageWidth + (row >= 1 ? row * margin : 0);
} else {
images[i].style.left = row * imageWidth + (row >= 1 ? (row + 0.5) * margin : 0);
}
row++
} else {
images[i].style.left = 0;
row = 1;
rowNum++;
}
if (rowNum > 0) {
column.push(column[i-rowWidth] + images[i].scrollHeight + margin);
} else {
column.push(images[i].scrollHeight);
}
}
mosaic.style.height = mosaic.scrollHeight;
}
var change = function() {
var elem = document.getElementsByTagName("body");
var count = 0;
count++;
var color = "";
var colors = ["#ff6051", "#ff9f51", "#ffdf51", "#b6ff51", "#51adff", "#3e65c1", "#6414ef"];
for (var i = 0; i < colors.length; i++) {
if (count == i + 1) {
color = colors[i];
}
}
elem[0].style.backgroundColor = color;
}
<button onclick="change()">Click me</button>
I want the background color of the body to change when I click the button.
But the number of variable "count" doesn't seem to increase. What should I do to make the number increase?
Declare the variabe count outside the function so that it gets the global scope whenever you update.
DEMO
var count = 0;
var change = function() {
var elem = document.getElementsByTagName("body");
count++;
console.log('##count',count);
var color = "";
var colors = ["#ff6051", "#ff9f51", "#ffdf51", "#b6ff51", "#51adff", "#3e65c1", "#6414ef"];
for (var i = 0; i < colors.length; i++) {
if (count == i + 1) {
color = colors[i];
}
}
elem[0].style.backgroundColor = color;
}
<button onclick="change()">Click me</button>
Without changing your code too much, you can avoid the loop and iterate over the array by comparing the current color. This also avoids holding a count iterator.
Note:
Some browsers return backgroundColor as an rgb value (e.g., rgb( ###, ###, ###)), which is why rgb2hex is used to convert the it to the hex value like that stored in the colors array.
var change = function() {
var el = document.querySelector("body");
var colors = ["#ff6051", "#ff9f51", "#ffdf51", "#b6ff51", "#51adff", "#3e65c1", "#6414ef"];
var currentColor = rgb2hex( el.style.backgroundColor );
var colorIndex = colors.indexOf( currentColor );
// If at last color, cycle back to front
if (colorIndex == colors.length-1)
colorIndex = -1;
el.style.backgroundColor = colors[colorIndex + 1];
}
/** Converts decimal to hex **/
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
/** Converts rgb string to hex string **/
function rgb2hex(rgb) {
if (rgb.search("rgb") == -1)
return rgb;
else {
rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))?\)$/);
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
}
<button onclick="change()">Click me</button>
Each call to your function is resetting count to 0 because you are setting it to zero on the second line of the function.
If you set it to 0 outside the function once, this will solve the count problem.
However, there is an additional problem (that you didn't mention): after counting to 7, you run out of colours in your array because count exceeds the bounds of the array. I would lose the for loop since it is unnecessary (use count to index into the array instead) and just reset count when it reaches the size of the array.
var count = 0;
var colors = ["#ff6051", "#ff9f51", "#ffdf51", "#b6ff51", "#51adff", "#3e65c1", "#6414ef"];
var change = function() {
var elem = document.getElementsByTagName("body");
if (count == colors.length) {
count = 0;
}
elem[0].style.backgroundColor = colors[count];
count++;
}
<button onclick="change()">Click me</button>
The count variable has local scope, so it will not exist after the anonymous function expression referred by change variable finishes execution. For it to sustain its life time across repeated function calls on button click action it should be declared in global scope outside the anonymous function expression:
var count = 0; //now count has global scope.
var change = function() {
var elem = document.getElementsByTagName("body");
count++;
var color = "";
var colors = ["#ff6051", "#ff9f51", "#ffdf51", "#b6ff51", "#51adff", "#3e65c1", "#6414ef"];
for (var i = 0; i < colors.length; i++) {
if (count == i + 1) {
color = colors[i];
}
}
elem[0].style.backgroundColor = color;
}
<button onclick="change()">Click me</button>
Note: You should also consider giving some default color inside the for loop as after 7 clicks it will be setting the backgroundColor to empty string.
var color = "#000000"; //default black color may be
To achieve expected result, use below option
No need of for loop
After 7 clicks , loop runs again
One issue with your code is, first background color will always be skipped due i+1 and after 7 clicks , it comes back to white background
var count = 0;
var colors = ["#ff6051", "#ff9f51", "#ffdf51", "#b6ff51", "#51adff", "#3e65c1", "#6414ef"];
var change = function() {
if(count == colors.length + 1){
count =0;
}
++count;
var elem = document.getElementsByTagName("body");
elem[0].style.backgroundColor = colors[count];
}
https://codepen.io/divyar34/pen/EbZxPm
You can generically count the invocations of any function by "lifting" the function (wrapping it) with a function that just does that.
This leaves you with a simple function that doesn't need to know if it is being counted, which you can wrap with a counter only when you need it.
function countingWrapper(f,reportf)
{
var counter = 0;
return function()
{
reportf( ++counter)
return f.apply(this, arguments);
}
}
function doSomething() { console.log('boo!'); }
function reportSomething(n) { console.log('did something '+ n + ' times.')}
var newDoSomething = countingWrapper(doSomething, reportSomething);
newDoSomething();
newDoSomething();
newSoSomething();
I am building a very simple memory game for a small project. The logic is as follows:
click on the input field to choose with how many pairs would you like to play
create divs with classes card1, card2 etc.
clone divs and randomize their place in the array
Here is my script (fork in JSFiddle):
$(".button").click(function () {
// get the value from the input
var numCards = parseInt($('input').val());
for (var i = 1; i <= numCards; i++) {
// create the cards
$(".container").append("<div class='card" + i + " cards'></div>") &&
$(".card" + i).clone().appendTo(".container");
}
// randomize cards in stack
var cards = $(".cards");
for (var i = 0; i < cards.length; i++) {
var target = Math.floor(Math.random() * cards.length - 1) + 1;
var target2 = Math.floor(Math.random() * cards.length - 1) + 1;
var target3 = Math.floor(Math.random() * cards.length - 1) + 1;
cards.eq(target).before(cards.eq(target2)).before(cards.eq(target3));
}
});
what I need now is to adjust the 3rd step, meaning to dynamically create the target vars, and the last line of the code
cards.eq(target).before(cards.eq(target2)).before(cards.eq(target3));
So please make me a suggestion - how would you do it? And bare in mind this is a project for beginners. Thank you!
$(".button").click(function () {
// get the value from the input
var numCards = parseInt($('input').val());
for (var i = 1; i <= numCards; i++) {
// create the cards
$(".container").append("<div class='card" + i + " cards'></div>") &&
$(".card" + i).clone().appendTo(".container");
}
var parent = $(".container");
var divs = parent.children();
while (divs.length) {
parent.append(divs.splice(Math.floor(Math.random() * divs.length), 1)[0]);
}
});
see jsfidle: http://jsfiddle.net/007y4rju/8/
source: http://jsfiddle.net/C6LPY/2/
Here is the version of the code in jsfiddle - http://jsfiddle.net/007y4rju/6/
Please, check if the behavior is consistent with the original code.
$(document).ready(function () {
$(".button").click(function () {
// get the value from the input
var numCards = parseInt($('input').val());
for (var i = 1; i <= numCards; i++) {
// create the cards
$(".container").append("<div class='card" + i + " cards'></div>") &&
$(".card" + i).clone().appendTo(".container");
}
// randomize cards in stack
var cards = $(".cards");
var startTarget = Math.floor(Math.random() * cards.length - 1) + 1;
var collection = cards.eq(startTarget);
var nextTarget;
var i;
for (i = 0; i < cards.length; i++) {
nextTarget = Math.floor(Math.random() * cards.length - 1) + 1;
collection.before(cards.eq(nextTarget));
}
});
});
You can randomize index in a class name (card%i%) when cloning divs. Then you don't need to shuffle cloned divs; you can append them as is.
$(".button").click(function () {
// get the value from the input
var numCards = parseInt($('input').val());
for (var i = 1; i <= numCards; i++) {
// create the cards
$(".container").append("<div class='card" + i + " cards'></div>");
}
var aIndices = [];
for (var i = 1; i <= numCards; i++) {
var ix;
do ix = Math.round(Math.random() * (numCards - 1)) + 1;
while (aIndices.indexOf(ix) >= 0);
aIndices.push(ix);
// clone
$(".card" + ix).clone().appendTo(".container");
}
});
I have a group of divs called "oponente", and if the user do not click on one of them in five seconds the script will choose one randomly. What I dont know how to do is to keep in LocalStorage which div the script had choose. Here is my script, I dont know what to write on 'key name' and so on.
$(document).ready(function () {
$(".oponente").addClass("gray");
var elements = $(".oponente");
var elementCount = elements.size();
var elementsToShow = 1;
var alreadyChoosen = ",";
var i = 0;
while (i < elementsToShow) {
var rand = Math.floor(Math.random() * elementCount);
if (alreadyChoosen.indexOf("," + rand + ",") < 0) {
alreadyChoosen += rand + ",";
setTimeout(function () {
elements.eq(rand).window.localStorage.setItem('key name', 'key name');
}, 5000);
++i;
}
}
});
You can store index of selected one. Then you can get child from the parent with that index number clearly.
Let me simulate
$(document).ready(function () {
$(".oponente").addClass("gray");
var elements = $(".oponente");
var elementCount = elements.size();
var elementsToShow = 1;
var alreadyChoosen = ",";
var i = 0;
while (i < elementsToShow) {
var rand = Math.floor(Math.random() * elementCount);
if (alreadyChoosen.indexOf("," + rand + ",") < 0) {
alreadyChoosen += rand + ",";
setTimeout(function () {
localStorage.setItem('indexOfSelected', selectedDiv.index());
}, 5000);
++i;
}
}
});
Then you can get the selected div with
var indexOfSelected = localStorage.getItem('indexOfSelected');
myDiv = parentDiv.children(":eq("+indexOfSelected+")")
All you need to init a selectedDiv then a parentDiv which is parent of the selectedDiv.
I am trying to render the list based on virtual rendering concept. I am facing some minor issues, but they are not blocking the behaviour. Here is the working fiddle http://jsfiddle.net/53N36/9/ and Here are my problems
Last items are not visible, I assume some where I missed indexing.(Fixed, Please see the edit)
How to calculate scrollPosition if I want to add custom scroll to this.
Is this the best method or any other?
I have tested it with 700000 items and 70 items in chrome. Below is the code
(function () {
var list = (function () {
var temp = [];
for (var i = 0, l = 70; i < l; i++) {
temp.push("list-item-" + (i + 1));
}
return temp;
}());
function listItem(text, id) {
var _div = document.createElement('div');
_div.innerHTML = text;
_div.className = "listItem";
_div.id = id;
return _div;
}
var listHold = document.getElementById('listHolder'),
ht = listHold.clientHeight,
wt = listHold.clientWidth,
ele = listItem(list[0], 'item0'),
frag = document.createDocumentFragment();
listHold.appendChild(ele);
var ht_ele = ele.clientHeight,
filled = ht_ele,
filledIn = [0];
for (var i = 1, l = list.length; i < l; i++) {
if (filled + ht_ele < ht) {
filled += ht_ele;
ele = listItem(list[i], 'item' + i);
frag.appendChild(ele);
} else {
filledIn.push(i);
break;
}
}
listHold.appendChild(frag.cloneNode(true));
var elements = document.querySelectorAll('#listHolder .listItem');
function MouseWheelHandler(e) {
var e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
console.log(delta);
//if(filledIn[0] != 0 && filledIn[0] != list.length){
if (delta == -1) {
var start = filledIn[0] + 1,
end = filledIn[1] + 1,
counter = 0;
if (list[start] && list[end]) {
for (var i = filledIn[0]; i < filledIn[1]; i++) {
if (list[i]) {
(function (a) {
elements[counter].innerHTML = list[a];
}(i));
counter++;
}
}
filledIn[0] = start;
filledIn[1] = end;
}
} else {
var start = filledIn[0] - 1,
end = filledIn[1] - 1,
counter = 0;
if (list[start] && list[end]) {
for (var i = start; i < end; i++) {
if (list[i]) {
(function (a) {
elements[counter].innerHTML = list[a];
}(i));
counter++;
}
}
filledIn[0] = start;
filledIn[1] = end;
}
}
//}
}
if (listHold.addEventListener) {
listHold.addEventListener("mousewheel", MouseWheelHandler, false);
listHold.addEventListener("DOMMouseScroll", MouseWheelHandler, false);
} else listHold.attachEvent("onmousewheel", MouseWheelHandler);
}());
Please suggest me on this.
EDIT:
I have tried again and I am able to fix the indexing issue. http://jsfiddle.net/53N36/26/
But how can I calculate the scroll position based on the array list currently displayed.
Is this the best method or any other?
I think something that would make this much easier is not to try to handle scrolling yourself.
In this fiddle I show that you can let the browser handle scrolling for you, even though we are using virtual rendering.
Using .scrollTop I detect where the browser thinks the user is looking, and I draw in items based on that.
You'll note that if you set hidescrollbar to false and the user uses it to scroll, my method still runs fine.
Therefore, to calculate scroll position you can just use .scrollTop.
And as for custom scrolling, just make sure you influence the .scrollTop of #listHolder and recall refreshWindow()
CODE FROM FIDDLE
(function () {
//CHANGE THESE IF YOU WANT
var hidescrollbar = false;
var numberofitems = 700000;
//
var holder = document.getElementById('listHolder');
var view = null;
//get the height of a single item
var itemHeight = (function() {
//generate a fake item
var div = document.createElement('div');
div.className = 'listItem';
div.innerHTML = 'testing height';
holder.appendChild(div);
//get its height and remove it
var output = div.offsetHeight;
holder.removeChild(div);
return output;
})();
//faster to instantiate empty-celled array
var items = Array(numberofitems);
//fill it in with data
for (var index = 0; index < items.length; ++index)
items[index] = 'item-' + index;
//displays a suitable number of items
function refreshWindow() {
//remove old view
if (view != null)
holder.removeChild(view);
//create new view
view = holder.appendChild(document.createElement('div'));
var firstItem = Math.floor(holder.scrollTop / itemHeight);
var lastItem = firstItem + Math.ceil(holder.offsetHeight / itemHeight) + 1;
if (lastItem + 1 >= items.length)
lastItem = items.length - 1;
//position view in users face
view.id = 'view';
view.style.top = (firstItem * itemHeight) + 'px';
var div;
//add the items
for (var index = firstItem; index <= lastItem; ++index) {
div = document.createElement('div');
div.innerHTML = items[index];
div.className = "listItem";
view.appendChild(div);
}
console.log('viewing items ' + firstItem + ' to ' + lastItem);
}
refreshWindow();
document.getElementById('heightForcer').style.height = (items.length * itemHeight) + 'px';
if (hidescrollbar) {
//work around for non-chrome browsers, hides the scrollbar
holder.style.width = (holder.offsetWidth * 2 - view.offsetWidth) + 'px';
}
function delayingHandler() {
//wait for the scroll to finish
setTimeout(refreshWindow, 10);
}
if (holder.addEventListener)
holder.addEventListener("scroll", delayingHandler, false);
else
holder.attachEvent("onscroll", delayingHandler);
}());
<div id="listHolder">
<div id="heightForcer"></div>
</div>
html, body {
width:100%;
height:100%;
padding:0;
margin:0
}
body{
overflow:hidden;
}
.listItem {
border:1px solid gray;
padding:0 5px;
width: margin : 1px 0px;
}
#listHolder {
position:relative;
height:100%;
width:100%;
background-color:#CCC;
box-sizing:border-box;
overflow:auto;
}
/*chrome only
#listHolder::-webkit-scrollbar{
display:none;
}*/
#view{
position:absolute;
width:100%;
}