Inconsistent Javascript behaviour (IF statement nested in while loop) - javascript

I'm trying to write a simple Javascript(jQuery) function that randomly displays 6 Divs out of a possible 11. The code sort-of-works, it does randomly display around about half of the Divs, but it varaies between 4 and 8.
Can anyone tell me where I'm going wrong? It seems it should be so simple yet I'm completely lost!
My code:
<div class="offer">Offer 1</div>
<div class="offer">Offer 2</div>
... snip
<div class="offer">Offer 11</div>
<script src="query.min.js" type="text/javascript"></script>
<script>
var changed = 0;
while (changed < 6) {
$('.offer').each(function(index) {
if (changed < 6) {
var showOrNot = Math.floor(Math.random() * 2);
if (showOrNot == 1) {
$(this).addClass('offershow');
changed += 1;
$(this).text(changed); //debugging looking for the current value of changed
}
}
})
}
</script>

The problem as it stands now is that you have a bunch of unrelated attempts. If you have a bucket with 11 balls and have a 50% chance to remove each ball, you could end up with any number of balls between 0 and 11. Probability is skewed toward the center, but you don't get six and exactly six each time.
What you want is to remove six, and exactly six, balls, selected arbitrarily.
Try something more like this:
var offers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
for (var i = 0; i < 6; i += 1) {
// choose a remaining offer at random
var index = Math.floor(Math.random() * offers.length);
// retrieve the item being shown
var item = $('.offer').eq(offers[index]);
item.addClass('offerShow');
// remove this offer from the list of possibilities
offers.splice(index, 1);
}
EDIT: In the comments, the OP clarified that what he really wants is to take an arbitrarily-sized list of offers and show six of them. The code provided below addresses that need, rather than the strict request in the original question. I'm leaving the original code for reference.
var OFFERS_TO_SHOW = 6; // config, of sorts
// make sure no offers are shown now
$('.offer').removeClass('offerShow');
// show offers selected at random
for (var i = 0; i < OFFERS_TO_SHOW; i += 1) {
// get a list of offers not already being shown
var candidates = $('.offer').not('.offerShow');
// select one from this list at random
var index = Math.floor(Math.random() * offers.length);
// show this offer by adding the offerShow class
candidates.eq(index).addClass('.offerShow');
}

I think the problem is that you're not excluding divs that you've already set to show. So it's possible that your code to pick the next div to show is choosing one that's already been shown. If that makes sense? Trying changing your selector.....
$('.offer').not('.offershow').each(........
Keep in mind that addClass doesn't remove the existing classes, so your original selector will still prove true even though you've added the offershow class.

You're actually not checking wheter the selected div is already shown or not. Means when you're looping through all divs, there is always a possibility of setting a div visible twice. The script now thinks it was a new one and sets changed += 1;
Try adding something like this:
if(!$(this).hasClass('offershow')) {
[[rest of code here]]
}

Related

How to design a line deleting function for Tetris game?

I am trying to build a Tetris game in javaScript and HTML. I ran into problems with my function for removing the full lines. It sort of works, but not every time and not as I expected. I am not a professional programmer, I am learning to code just for fun. My knowledge of javaScript is limited and my methods are primitive. My board is made of div tags. Each of them represents one square and has an id with unique number. Currently falling shape is stored by an array called “brick”. It contains four numbers, which corresponds to the div ids.
For example brick = [2, 2, 3, 5] means Tetris “l” piece positioned horizontally. There are 12 squares in a row. The first and last has the class “fender,” which means, that they cannot became part of the brick, and so the shape could not leave the board.
The fender squares are made invisible by CSS. The brick is falling by adding 12. From [2, 3, 4, 5] it is [14, 15, 16, 17], etc. The first four rows have display set to “none,” so the visible board starts by square number 63.
On the beginning, every square of the board (except the fender squares) has a class “free,” which means that it is permeable for falling shape. When it does not have the class, the shape can’t move through it. When the brick hits the bottom or another fallen shape, it is added to another variable, called pile.
This is done by a function called touchdown(). It does several other things. The color of the brick is stored in another variable called colorPile. For example when the blue brick [2, 3, 4, 5] lands, it adds to colorPile variable four times “blue”. Touchdown() also removes the class “free” from all the squares of the brick, so the next brick can’t fall through the occupied space. Then it calls for the creation of a new brick.
The deleting process starts with function called testLines():
// Look through all board lines
for (var i = 62; i < 300; i += 12) {
// Prepare empty array
line = [];
// Put numbers of occupied squares in the array.
for (var j = i; j < i + 10; j++) {
if (document.getElementById("sq" + j).classList.contains("free") === false) {
line.push(j);
}
}
// When the line is full, put its number in “numb” variable and call for deleting function
if (line.length == 10) {
numb = i;
clearLine(numb);
}
}
}
}
It goes through all lines of the board and look for the full ones. If there is a full line, it stores its number in a variable called “numb” and calls another function named clearLine():
function clearLine() {
// Put the indexes of the squares in the full line in a special array
indexes = [];
for (var i = numb; i < numb + 10; i++) {
indexes.push(pile.indexOf(i));
}
// Remove numbers on the indexes
pileindexor = indexes.length;
while (pileindexor--) {
pile.splice(indexes[pileindexor], 1);
}
// Add 12 to all numbers greater than the line number (= move them on row down)
for (var j = 0; j < pile.length; j++) {
if (pile[j] < numb) {
pile[j] = (pile[j] + 12);
}
}
clearColorPile();
paintPile();
clearBoard();
lines++;
testLines();
}
It also calls function called clearColorPile(), which removes colors on same indexes from the "colorPile" variable. Next it calls another function called paintPile(). The paintPile() function changes color of all squares in the "pile" variable according to the modified "colorPile" variable and removes “free” class from them.
Than the clearLine() calls another function called clearBoard(). It paints all squares not belonging to the pile white and ads the “free” class to them. Finally, the clearLine() function calls again the testLines() to look for more full rows.
This procedure is behaving as I expected for most of the time. But sometimes, the full line is not cleared completely. My game is here. You can see for yourselves. What have I done wrong? How to delete the full line?

For Loop Not Working As Expected When Manipulating Array of Objects

I have an array of objects that I am editing.
This array varies on user selection, but generally looks like this:
[{boxID:0,name:"Apple Bread",weight:2}]
This array is declared as $scope.boxes
At a high level, this array is derived from a previously called function, the point of which is to pack items (such as Apple Bread) into boxes by weight, with no box exceeding a total weight of (in this case) 6 pounds. This array is thus filled with objects which represent the item, its weight, and the boxID (0 being the first box) that it is packed into.
My problem is, I am inserting in functionality to have the user edit the box, so that they can shuffle things around and customize their box, while still respecting the total weight limit.
To than end I have a function that utilizes drag-and-drop functionality to let the users edit and move items around their boxes. Once they are done, I need to validate what item belongs where. But I can't even get the ondragstart function to work properly. The idea is ondragstart to remove the item in question from the array, and then recalculate all the boxes, through the following:
$scope.boxyboxybox2 = function() {
$scope.displayBoxes = [];
for (var i = 0; i < $scope.boxes.length; i++) {
if (i == 0) {
$scope.displayBoxes.push({
box: $scope.boxes[i].boxID,
contents: [{
name: $scope.boxes[i].name,
weight: $scope.boxes[i].weight
}]
});
console.log($scope.displayBoxes);
continue;
}
for (var z = 0; z < $scope.displayBoxes.length; z++) {
if ($scope.boxes[i].boxID == $scope.displayBoxes[z].box) {
$scope.displayBoxes[z].contents.push({
name: $scope.boxes[i].name,
weight: $scope.boxes[i].weight
})
console.log($scope.boxes[i].boxID, $scope.displayBoxes[z].box, $scope.displayBoxes);
} else {
$scope.displayBoxes.push({
box: $scope.boxes[i].boxID,
contents: [{
name: $scope.boxes[i].name,
weight: $scope.boxes[i].weight
}]
});
console.log($scope.displayBoxes);
break;
}
}
}
console.log($scope.displayBoxes);
}
The above doesn't work, I'm not utilizing breaks or something properly, because variations of this either get an infinite loop or I have an array of objects (in displayBoxes which is what goes back to the DOM) where I have duplicates, as in I have multiple instances of Box 3, for no apparent reason.
Having a hard time figuring this out. I can make a plunker if necessary but I'm hoping its just a small oversight that is apparent here, as I am quite close to my intended result.
Edit: Plunker as requested: https://plnkr.co/edit/Bq6rgeOx26QTTu8d7AXh?p=preview
The answer was to hard code z as less than the length of the displayBoxes array.
for (var z = $scope.displayBoxes.length-1; z < $scope.displayBoxes.length; z++) {
This allowed the break; to be effective while maintaining the desired functionality.

Identifying edge cases of a one-dimensional array in Javascript

I'm creating a 2-dimensional heat map which has functionality when you click on any pixel. It grabs data associated with the index of every pixel (including adjacent pixels) and plots it. It currently looks like this:
The problem that I'm encountering is when I click on a left or right edge pixel, since it grabs data from adjacent pixels, it can retrieve data from the opposite side of the graph since it is all within a one-dimensional array. I am trying to create a conditional which checks if the clicked pixel is an edge case, and then configures the magnified graph accordingly to not show points from the other side of the main graph. This is the code I have so far:
// pushes all dataMagnified arrays left and right of i to magMainStore
var dataGrabber = function(indexGrabbed, arrayPushed) {
// iterates through all 5 pixels being selected
for (var b = -2; b <= 2; b++) {
var divValue = toString(i / cropLength + b);
// checks if selected index exists, and if it is not in the prior row, or if it is equal to zero
if (dataMagnified[indexGrabbed + b] != undefined && (& divValue.indexOf(".")!=-1)) {
dataMagnified[indexGrabbed + b].forEach(function(z) {
arrayPushed.push(z);
})
}
}
};
I am trying to get the same result as if I had a two dimensional array, and finding when the adjacent values within a single array is undefined. This is the line where I'm creating a conditional for that
if (dataMagnified[indexGrabbed + b] != undefined && (& divValue.indexOf(".")!=-1)) {
The second condition after the and is my attempts so far trying to figure this out. I'm unsure if I can even do this within a for loop that iterates 5 times or if I have to create multiple conditions for this. In addition, here's an image displaying what I'm trying to do:
Thank you!
Your approach looks overly complex and will perform rather slowly. For example, converting numbers to strings to be able to use .indexOf() to find a decimal point just for the sake of checking for integer numbers doesn't seem right.
A much simpler and more elegant solution might be the following function which will return the selection range bounded by the limits of the row:
function getBoundedSelection(indexGrabbed, selectionWidth) {
return dataMagnified.slice(
Math.max(Math.floor(indexGrabbed/cropLength) * cropLength, indexGrabbed - selectionWidth),
Math.min(rowStartIndex + cropLength, indexGrabbed + selectionWidth)
);
}
Here, to keep it as flexible as possible, selectionWidth determines the width of the selected range to either side of indexGrabbed. This would be 2 in your case.
As an explanation of what this does, I have broken it down:
function getBoundedSelection(indexGrabbed, selectionWidth) {
// Calculate the row indexGrabbed is on.
var row = Math.floor(indexGrabbed/cropLength);
// Determine the first index on that row.
var rowStartIndex = row * cropLength;
// Get the start index of the selection range or the start of the row,
// whatever is larger.
var selStartIndex = Math.max(rowStartIndex, indexGrabbed - selectionWidth);
// Determine the last index on that row
var rowEndIndex = rowStartIndex + cropLength;
// Get the end index of the selection range or the end of the row,
//whatever is smaller.
var selEndIndex = Math.min(rowEndIndex, indexGrabbed + selectionWidth);
// Return the slice bounded by the row's limits.
return dataMagnified.slice(selStartIndex, selEndIndex);
}
So I discovered that since the results of the clicked position would create a variable start and end position in the for loop, the only way to do this was as follows:
I started the same; all the code is nested in one function:
var dataGrabber = function(indexGrabbed, arrayPushed) {
I then create a second function that takes a start and end point as arguments, then passes them as the for loop starting point and ending condition:
var magnifyCondition = function (start, end) {
for (var b = start; b <= end; b++) {
if (dataMagnified[indexGrabbed + b] != undefined) {
dataMagnified[indexGrabbed + b].forEach(function (z) {
arrayPushed.push(z);
})
}
}
};
After that, I created 5 independent conditional statements since the start and end points can't be easily iterated through:
if (((indexGrabbed - 1) / cropLength).toString().indexOf(".") == -1) {
magnifyCondition(-1, 2);
}
else if ((indexGrabbed / cropLength).toString().indexOf(".") == -1) {
magnifyCondition(0, 2);
}
else if (((indexGrabbed + 1) / cropLength).toString().indexOf(".") == -1) {
magnifyCondition(-2, 0);
}
else if (((indexGrabbed + 2) / cropLength).toString().indexOf(".") == -1) {
magnifyCondition(-2, 1);
}
else {
magnifyCondition(-2, 2);
}
};
Lastly, I pass the index grabbed (i of the on clicked function) and an arbitrary array where the values get stored.
dataGrabber(i, magMainStore);
If there's a better way instead of the if statements, please let me know and I'd be happy to organize it better in the future!

Optimal way of showing/hiding images in JavaScript

I read this question here "Is it faster to swap an img src or show/hide multiple images?"
The answer to the question has two options.
1) Alter the image source - Page loads faster, swapping consumes time.
2) Pre load images and then simply show/hide - Page loads a little slower, swapping is quicker
To my problem, I go with the second option as load time is 2nd priority for me. But is there best or most optimal way of writing the code?
Let's say I keep getting numbers (10,11,15,25,13,19 etc randomly) from somewhere. I have to show that many images (small dots, 30 in number, each image about 1kb).. I also have the condition, every dot represents 1.5. So I have written the following code.
var dots = new Array(30);
function onBodyLoad() {
for(var j=0;j<30;j++)
dots[j] = document.getElementById("dotimg"+j);
}
//called by some method every second.
//argument: the value received from somewhere.
function updateImages(s) {
var x = Math.round(s);
var dotPos = x/1.5;
for(var i=0;i<dotPos;i++) {
dots[i].style.visibility='visible'; //show that many dots
document.getElementById('dot-val').textContent=s;//value received shown in span
}
for(var j=dotPos;j<30;j++) dots[j].style.visibility='hidden';//hide remaining dots
}
So if the value received is 25 in one second, 17 dots will be shown.. if in the next second the value received is 15, 10 dots will be shown, the rest will be hidden. Is there a better way of writing the above code?
First of all, move the 'dot-val' setting out of the for loop (You're assigning the same value to it in each iteration of the for loop).
Also, you can change the display states in 1 loop, saving a for:
function updateImages(s) {
var x = Math.round(s);
var dotPos = x/1.5;
for(var i=0;i<30;i++) {
if(i < dotPos){
dots[i].style.display='inline-block'; // Assuming they're inline-block elements.
}else{
dots[i].style.display='none';
}
}
document.getElementById('dot-val').textContent=s;//value received shown in span
}
Now, if you really want to trim your code, skip the temp x var, and use a Ternary operator:
function updateImages(s) {
var dotPos = Math.round(s)/1.5;
for(var i=0;i<30;i++) {
dots[i].style.display = (i < dotPos)? 'inline-block' : 'none';
}
document.getElementById('dot-val').textContent = s;//value received shown in span
}

alternatives for excessive for() looping in javascript

Situation
I'm currently writing a javascript widget that displays a random quote into a html element. the quotes are stored in a javascript array as well as how many times they've been displayed into the html element. A quote to be displayed cannot be the same quote as was previously displayed. Furthermore the chance for a quote to be selected is based on it's previous occurences in the html element. ( less occurrences should result in a higher chance compared to the other quotes to be selected for display.
Current solution
I've currently made it work ( with my severely lacking javascript knowledge ) by using a lot of looping through various arrays. while this currently works ( !! ) I find this solution rather expensive for what I want to achieve.
What I'm looking for
Alternative methods of removing an array element from an array, currently looping through the entire array to find the element I want removed and copy all other elements into a new array
Alternative method of calculating and selecting a element from an array based on it's occurence
Anything else you notice I should / could do different while still enforcing the stated business rules under Situation
The Code
var quoteElement = $("div#Quotes > q"),
quotes = [[" AAAAAAAAAAAA ", 1],
[" BBBBBBBBBBBB ", 1],
[" CCCCCCCCCCCC ", 1],
[" DDDDDDDDDDDD ", 1]],
fadeTimer = 600,
displayNewQuote = function () {
var currentQuote = quoteElement.text();
var eligibleQuotes = new Array();
var exclusionFound = false;
for (var i = 0; i < quotes.length; i++) {
var iteratedQuote = quotes[i];
if (exclusionFound === false) {
if (currentQuote == iteratedQuote[0].toString())
exclusionFound = true;
else
eligibleQuotes.push(iteratedQuote);
} else
eligibleQuotes.push(iteratedQuote);
}
eligibleQuotes.sort( function (current, next) {
return current[1] - next[1];
} );
var calculatePoint = eligibleQuotes[0][1];
var occurenceRelation = new Array();
var relationSum = 0;
for (var i = 0; i < eligibleQuotes.length; i++) {
if (i == 0)
occurenceRelation[i] = 1 / ((calculatePoint / calculatePoint) + (calculatePoint / eligibleQuotes[i+1][1]));
else
occurenceRelation[i] = occurenceRelation[0] * (calculatePoint / eligibleQuotes[i][1]);
relationSum = relationSum + (occurenceRelation[i] * 100);
}
var generatedNumber = Math.floor(relationSum * Math.random());
var newQuote;
for (var i = 0; i < occurenceRelation.length; i++) {
if (occurenceRelation[i] <= generatedNumber) {
newQuote = eligibleQuotes[i][0].toString();
i = occurenceRelation.length;
}
}
for (var i = 0; i < quotes.length; i++) {
var iteratedQuote = quotes[i][0].toString();
if (iteratedQuote == newQuote) {
quotes[i][1]++;
i = quotes.length;
}
}
quoteElement.stop(true, true)
.fadeOut(fadeTimer);
setTimeout( function () {
quoteElement.html(newQuote)
.fadeIn(fadeTimer);
}, fadeTimer);
}
if (quotes.length > 1)
setInterval(displayNewQuote, 10000);
Alternatives considered
Always chose the array element with the lowest occurence.
Decided against this as this would / could possibly reveal a too obvious pattern in the animation
combine several for loops to reduce the workload
Decided against this as this would make the code to esoteric, I'd probably wouldn't understand the code anymore next week
jsFiddle reference
http://jsfiddle.net/P5rk3/
Update
Rewrote my function with the techniques mentioned, while I fear that these techniques still loop through the entire array to find it's requirements, at least my code looks cleaner : )
References used after reading the answers here:
http://www.tutorialspoint.com/javascript/array_map.htm
http://www.tutorialspoint.com/javascript/array_filter.htm
http://api.jquery.com/jQuery.each/
I suggest array functions that are mostly supported (and easily added if not):
[].splice(index, howManyToDelete); // you can alternatively add extra parameters to slot into the place of deletion
[].indexOf(elementToSearchFor);
[].filter(function(){});
Other useful functions include forEach and map.
I agree that combining all the work into one giant loop is ugly (and not always possible), and you gain little by doing it, so readability is definitely the winner. Although you shouldn't need too many loops with these array functions.
The answer that you want:
Create an integer array that stores the number of uses of every quote. Also, a global variable Tot with the total number of quotes already used (i.e., the sum of that integer array). Find also Mean, as Tot / number of quotes.
Chose a random number between 0 and Tot - 1.
For each quote, add Mean * 2 - the number of uses(*1). When you get that that value has exceeded the random number generated, select that quote.
In case that quote is the one currently displayed, either select the next or the previous quote or just repeat the process.
The real answer:
Use a random quote, at the very maximum repeat if the quote is duplicated. The data usages are going to be lost when the user reloads/leaves the page. And, no matter how cleverly have you chosen them, most users do not care.
(*1) Check for limits, i.e. that the first or last quota will be eligible with this formula.
Alternative methods of removing an array element from an array
With ES5's Array.filter() method:
Array.prototype.without = function(v) {
return this.filter(function(x) {
return v !== x;
});
};
given an array a, a.without(v) will return a copy of a without the element v in it.
less occurrences should result in a higher chance compared to the other quotes to be selected for display
You shouldn't mess with chance - as my mathematician other-half says, "chance doesn't have a memory".
What you're suggesting is akin to the idea that numbers in the lottery that haven't come up yet must be "overdue" and therefore more likely to appear. It simply isn't true.
You can write functions that explicitly define what you're trying to do with the loop.
Your first loop is a filter.
Your second loop is a map + some side effect.
I don't know about the other loops, they're weird :P
A filter is something like:
function filter(array, condition) {
var i = 0, new_array = [];
for (; i < array.length; i += 1) {
if (condition(array[i], i)) {
new_array.push(array[i]);
}
}
return new_array;
}
var numbers = [1,2,3,4,5,6,7,8,9];
var even_numbers = filter(numbers, function (number, index) {
return number % 2 === 0;
});
alert(even_numbers); // [2,4,6,8]
You can't avoid the loop, but you can add more semantics to the code by making a function that explains what you're doing.
If, for some reason, you are not comfortable with splice or filter methods, there is a nice (outdated, but still working) method by John Resig: http://ejohn.org/blog/javascript-array-remove/

Categories