How to retrigger a function with timeOuts via key input - javascript

For some time already I'm struggling with following code:
$(document).ready(function(){
keyBind();
});
var set = [];
var start = 0;
var total = 3;
var timeout = 1000;
function displayImages(i, total){
var pixBox = $('#picture-box');
var imgPre = 'resources/exhibit-';
var imgExt = '.png';
var sndExt = '.wav';
var imgSrc = '<img class="image" src="' + imgPre + i + imgExt +'">';
var sndSrc = new Audio(imgPre + i + sndExt);
var magic = ((i+1)%total) + ',' + total;
sndSrc.play();
pixBox.append(imgSrc);
var sT = setTimeout('displayImages(' + magic + ')', timeout);
set.push(sT);
if(sT >= total+1){
sndSrc.pause();
}
if(sT === total+1){
clearTimeout(sT);
// $('img').remove();
$('img:not(:last-child)').remove();
}
return false;
}
function keyBind(){
$(document).keydown(function(e) {
switch (e.which) {
case 75: //k
displayImages(start, total);
break;
case 80: //p
clearTimeout(set);
break;
default:
return;
}
e.preventDefault();
});
}
It's a kind of very primitive slideshow with accompanying sound files for each picture/slide. As you can see, it's controlled by keyboard input (keyBind function). And it all works fine, but only for the first time. When I trigger it fresh, timeOut function is doing it job and both if statements (first responsible for cutting the sound after the last file, second responsible for wrapping images back to first one after last is displayed) are fireing up and all is well.
That is, until I want to restart the sequence again, without refreshing. If I do that, soundfiles are going mute and the image sequence turns into an endless loop.
I've already tried dividing start and stop of sequence in separate functions, I tried to clear the div and reset the sound at the start of sequence and I did tried reseting the timeOut at the beggining too. All to no avail.
Do you, good and dear people of SO, have any idea what's wrong with my code and can shed some light on it? It's gonna be a lifesaver.
EDIT
It looks like setTimeout(sT) is not working. I've put a console.log after it and sT is not 0, it still has ID of last iteration. What may be the cause? What am I doing wrong?

Some issues:
You use the value that setTimeout returns as if it has some meaning (incremental), but the
actual value is implementation dependent. You should only use it for passing it to clearTimeout,
but not for things like the following:
if(sT >= total+1)
It is also not clear why you would want to add them to the set array,
as only the last one really is pending. All the others have already expired, since you
"chain" calls to setTimeout.
You clear a time-out right after setting one, while there is nothing you did not already know when setting it. So why not avoiding the setTimeout when you would be clearing right after?
Also, the argument to clearTimeout should be a number. It cannot be an array like set:
case 80: //p
clearTimeout(set);
You have a start variable, but ignore it when doing (i+1)%total.
Although not a problem here, you should avoid using strings as arguments to setTimeout, as it is just as evil as eval that way.
Here is a version of your code that fixes several of these issues:
$(document).ready(function(){
keyBind();
});
var sT = -1;
var total = 3;
var timeout = 1000;
function displayImages(i){
var pixBox = $('#picture-box');
var imgPre = 'resources/exhibit-';
var imgExt = '.png';
var sndExt = '.wav';
var imgSrc = '<img class="image" src="' + imgPre + i + imgExt +'">';
var sndSrc = new Audio(imgPre + i + sndExt);
i = (i+1)%total;
sndSrc.play();
pixBox.append(imgSrc);
if(i == 0){
sT = -1;
sndSrc.pause();
$('img:not(:last-child)').remove();
} else {
sT = setTimeout(displayImages.bind(null, i), timeout);
}
return false;
}
function keyBind(){
$(document).keydown(function(e) {
switch (e.which) {
case 75: //k
displayImages(0);
break;
case 80: //p
clearTimeout(sT);
break;
default:
return;
}
e.preventDefault();
});
}
I am not quite sure when you actually want to stop the sound and remove the images (except one). You might still need to change this code a bit.

EDIT: Adjusted according to correcting comment below:
You should not pass arguments to a function directly using settimeout. This has bugged me out a number of times. See this old post for a solution to this particular part of your code: How can I pass a parameter to a setTimeout() callback?

Related

prompt() in a loop never shows writeln() content

I am new to javascript, having trouble with this assignment. My professor suggested the problem was due to needing to parseInt, but even after I added the parseInt, it still isn't working.
This displays fine in Firefox, and the "higher" and "lower" statements are displayed, but when I run it in Chrome or Edge, only the window asking for a number will render. I did look for help, but I cant see what I'm doing wrong. Most of the suggestions Ive seen online, don't address the code directly. Is this problem specific code related or something else?
function play() {
let guess;
let randNum = Math.floor(1 + Math.random() * 999);
let guessed = false;
while (guessed == false) {
guess = window.prompt("Enter a number from 1 to 1000");
parseInt(guess);
if (guess == randNum) {
document.writeln("<li>" + "Congratulations! You guessed the correct number!</li>");
guessed = true;
document.writeln("</ol>");
document.writeln("Your guess: " + guess);
document.writeln("Actual number: " + randNum);
} else if (guess > randNum) {
document.writeln("<li>" + guess + " is Too High. Try Again.</li>");
document.writeln("</ol>");
} else if (guess < randNum) {
document.writeln("<li>" + guess + " is Too Low. Try Again.</li>");
document.writeln("</ol>");
}
}
}
window.addEventListener("load", play, false);
Don't use writeln. Use the proper methods like .append() or .insertAdjacentElement() or .insertAdjacentHTML() if you just want to insert HTML strings instead of Nodes.
Don't use alert, prompt, etc. Those Window methods will most likely get deprecated or at least discouraged in the near future.
Remember, use let for variables, and const for constants.
Use DOM methods like Element.querySelector() to get and store a DOM Element,
Use Document.createElement() to create a new one (a new LI Element in your case)
To ease the querying or creation of the desired DOM Elements — create two reusable functions:
// DOM utility functions:
const find = (selector, parent) => (parent || document).querySelector(selector);
const create = (tag, properties) => Object.assign(document.createElement(tag), properties);
that can be used to cache your Elements and use them later in your game logic
// Cache your DOM elements!
const elNumber = find("#number");
const elCheck = find("#check");
const elAnswers = find("#answers");
which will target and cache your three HTML elements by theri id attribute selector. As said above, instead of using prompt, use a better and less invasive UI (User interface) right into your App:
Enter a number from 1 to 10: <input id="number" type="text">
<button id="check" type="button">CHECK</button>
<ol id="answers"></ol>
Then create two let variables for the guessed state and one for the random number, so that when you start a new game you can change their values:
// Make available for multiple games!
let numRand;
let isGuessed;
then, giving your specific game, you need two more functions, one to start (and restart) the game and one for your game logic:
// Call this function to start a new game!
const start = () => {
// Clear old answers
// Reset old guessed state
// Generate a new random number
};
// Call this function on button CHECK click!
const check = () => {
// Game logic goes here!
}
// Assign listener to button:
elCheck.addEventListener("click", check);
// Start a new game!
start();
Demo time:
// DOM utility functions:
const find = (selector, parent) => (parent || document).querySelector(selector);
const create = (tag, properties) => Object.assign(document.createElement(tag), properties);
// Task:
// Cache your DOM elements!
const elNumber = find("#number"); // PS: remember, use const for constants!
const elCheck = find("#check");
const elAnswers = find("#answers");
// Make available for multiple games!
let numRand;
let isGuessed; // Try to prefix boolean variables with "is*"
// Call this function to start a new game!
const start = () => {
// Clear old answers:
elAnswers.innerHTML = "";
// Reset old guessed state
isGuessed = false;
// Generate a new random number 1 to 10:
numRand = Math.floor(Math.random() * 9) + 1;
};
// Call this function on button CHECK click!
const check = () => {
// Start a new game if needed
if (isGuessed) start();
// Get the user entered value
// Use parseInt with radix 10 and Math.abs
// to prevent negative numbers
const numUser = Math.abs(parseInt(elNumber.value, 10));
// Do nothing if invalid value entered:
if (!numUser) return;
// Update isGuessed state
isGuessed = numRand === numUser;
// Handle answer:
const textAnswer = `
You guessed: ${numUser}.
The number is ${isGuessed ? "correct!" : numUser > numRand ? "too high." : "too low."}
${isGuessed ? "Congratulations!" : "Try again"}
`;
// Create a LI element with the answer text
const elAnswer = create("li", {
textContent: textAnswer
});
// Append your LI element!
elAnswers.append(elAnswer);
// Clear the current value from input:
elNumber.value = "";
};
// Assign listener to button:
elCheck.addEventListener("click", check);
// Start a new game!
start();
Enter a number from 1 to 10: <input id="number" type="text">
<button id="check" type="button">CHECK</button>
<ol id="answers"></ol>
Additional learning resources:
Arrow_functions (MDN)
Template literals /Template strings (MDN)
Conditional (ternary) operator (MDN)
As I understand it, JavaScript is single-threaded, meaning that it has only one execution thread. Methods like prompt() and alert() block that thread. As long as prompt() is called in a loop, writeln content will not be rendered on the page.
One way to address this with your existing code is to use setTimeout() to delay the next call to prompt(), which allows content to be rendered in the meantime.
That being said, I recommend a more asynchronous method that does not rely on prompt() or writeln(). This answer is intended to explain the issue with your existing code; for a more robust strategy, see Roko's.
var randNum = Math.floor(1 + Math.random() * 999);
function ask() {
let guess = parseInt(window.prompt("Enter a number from 1 to 1000"));
if (guess == randNum) {
document.writeln("<div>Congratulations! You guessed the correct number!</div>");
document.writeln("<div>Your guess: " + guess + ". Actual number: " + randNum + "</div>");
} else if (guess > randNum) {
document.writeln("<div>" + guess + " is Too High. Try Again.</div>");
setTimeout(ask, 50);
} else if (guess < randNum) {
document.writeln("<div>" + guess + " is Too Low. Try Again.</div>");
setTimeout(ask, 50);
}
}
window.addEventListener("load", ask, false);

Infinte loop is killing my NodeJS server

As the title says, I think my server can't handle the speed of calculations of the infinite loop, how would I make it so the calculations still go very quick but the server doesn't crash? also when I try this in jsfiddle it eventually crashes my browser.
Here's the code:
<script>
function crashPoint(){
var currentTry = 2;
var mainMultplier = 1;
var secMultiplier = Math.random();
for(;;){
var randomInt = Math.floor(Math.random() * 100) + 1;
if(1/currentTry*100 < randomInt){
currentTry = currentTry+1;
mainMultplier = mainMultplier+1;
}else{
console.clear();
break;
}
}
var totalMultiplier = mainMultplier+secMultiplier;
if(totalMultiplier > 2){
totalMultiplier-1;
}
console.log("Crashed # " + totalMultiplier.toFixed(2));
}
</script>
<button onclick="crashPoint()">
Try me!
</button>
Kind regards.
Have you tried to use setInterval instead of a loop... just call the same function however many times per second you need it to execute.
1/currentTry*100 won't always stay an integer, randomInt will, I just had to make it so randomInt wasn't always an integer anymore which I could do by removing the *100.

How to create a Scramble function in Javascript

I'm am trying to create a simple slider game using javascript. It's i simple 4 by 4 number slider game with each button being labeled 1-15 with the last block being a blank block. I just have no idea on how to scramble the buttons in a random order to start the game.
Below is the code I currently have.
<body>
<h1> Slider Game </h1>
<script type="text/javascript">
var blankrow = 3;
var blankcol = 3;
for (var r=0; r<4; r++)
{
for (var c=0; c<4; c++)
{
var bid = "b"+r+c;
var val = 4*r+c+1;
if (bid==="b33")
val = ' ';
var s = '<input type = "button" id = "' + bid + '" value = "'
+ val + '" onclick = "makeMove(this.id);" />' + '\n';
document.write (s);
}
}
</script>
<input type = "button" id = "btnScramble" value = "Scramble" onclick = "scrambleBoard();"/>
<input type = "button" id = "btnReset" value = "Reset Board" onclick = "resetBoard();"/>
</body>
I created a function like this:
function scrambleBoard()
{
}
I just have no idea where to go from here. I am just learning Javascript so I am still learning how to code. Thanks!
Update:
This is the make move function I have
function makeMove(btnid)
{
//is btnid next to blank
var r = btnid.substr(1,1);
var c = btnid.substr(2,2);
if (blankrow==r && blankcol==c+1) // check right
{
blankid="b"+r+c;
document.getElementById(blankid).value = document.getElementById(btnid).value;
document.getElementById(btnid).value = ' ';
blankcol=c;
}
else if (blankrow==r && blankcol==c-1) // check left
{
blankid="b"+r+c;
document.getElementById(blankid).value = document.getElementById(btnid).value;
document.getElementById(btnid).value = ' ';
blankcol=c;
}
else if (blankrow==r+1 && blankcol==c) // check bottem
{
blankid="b"+r+c;
document.getElementById(blankid).value = document.getElementById(btnid).value;
document.getElementById(btnid).value = ' ';
blankrow=r;
}
else if (blankrow==r-1 && blankcol==c) // check top
{
blankid="b"+r+c;
document.getElementById(blankid).value = document.getElementById(btnid).value;
document.getElementById(btnid).value = ' ';
blankrow=r;
} else
alert("Move is invalid");
}
Now with this how would I take the function (makeMove) and put it into the scramble function. Sorry I am really having a hard time understanding this concept.
You will need a makeMove function that fills the hole from a selected direction anyway, in order to make the game. Scramble is very simple: repeat the makeMove operation a sufficient number of times with a random neighbour (ignoring invalid neighbours like sliding from left at the left edge).
EDIT: Style-wise, document.write is considered to be a bad practice. Much better would be to make an element such as
<div id="board"></div>
and then fill it up by either creating documents with document.createElement and adding it there, which is a bit of a pain, or you can go the easy route and assign HTML markup to innerHTML:
document.getElementById('board').innerHTML = allMyButtonsHTML;
Also, using onclick="..." is considered a bad practice; try to get used to not mixing JavaScript and HTML by simply leaving off the onclick="...", and instead assigning it from JavaScript:
var scrambleButton = document.getElementById('btnScramble');
scrambleButton.addEventListener('click', function() {
...
});
None of this is an error as it stands, but it will result in cleaner, more maintainable code in the future.
EDIT2: You would not be putting makeMove into the shuffle, you'd be calling it from there.
function shuffleBoard() {
// the hole starts here
var holeRow = 3, holeCol = 3;
// the number of shuffling moves
var moves = 100;
// repeat while moves is not yet at zero
loop: while (moves) {
// we want to move one space from our current hole, so start there
var nextRow = holeRow, nextCol = holeCol;
// get a random number from 0 to 3 using the |0 hack
// to convert a real number to an integer
var direction = (Math.random() * 4)|0;
// now to see what coordinate changes...
switch (direction) {
case 0:
// if we're going right, we increment the column
// if that puts us too far right, we jump to the start of the loop
// to pick a new direction again
if (nextCol++ > 3) continue loop;
break;
case 1:
// same deal for down
if (nextRow++ > 3) continue loop;
break;
case 2:
// and left
if (nextCol-- < 0) continue loop;
break;
case 3:
// and up
if (nextRow-- > 0) continue loop;
break;
}
// this should be more elegant but
// like this it will fit in with your existing function
makeMove('b' + nextRow + nextCol);
// now since we moved the hole, we update its current position
holeRow = nextRow;
holeCol = nextCol;
// that's one move down, lots to go!
moves--;
}
// or is it? nope, all done.
}
After you add the first button, loop through however many more you need to create. Use math.random to pick a random number 0-1. If the number is 0, use insertadjacenthtml to add a new button to the left. If the number is 1, add the button to the right.

Run function after each() jQuery

I know that the each() in jQuery is synchronous, but it doesn't seem to behaving that way. When I do this:
$('#findRankBtn').click(function () {
var websiteURL = $('#websiteURL').val();
var searchTerms = $('#searchTerm').val();
var pageNumber = $('#currentPage').val();
//INSERTS A + FOR EVERY SPACE
var searchTerms = searchTerms.replace(" ", "+");
searchGoogle(searchTerms, pageNumber, websiteURL);
});
function searchGoogle(searchTerms, pageNumber, websiteURL) {
$.getJSON("https://www.googleapis.com/customsearch/v1?key=AIzaSyDPuIrijE0IQ6330vMLN2p-L_4J6y_G60c&cx=013036536707430787589:_pqjad5hr1a&q=" + searchTerms + "&alt=json&start=" + pageNumber,
function (recievedData) {
//console.log(recievedData);
$.each(recievedData.items, function (i, item) {
$('#resultsDiv').append('<p class="resultLink">' + item.link + '</p>');
var linkAddress = $('.resultLink:last').text();
if (linkAddress.indexOf(websiteURL) !== -1) {
alert('found');
$('.resultLink:last').attr('class', 'yourLink');
$('#ifFound').attr('value', 'true');
}
});
var ifFound = $('#ifFound').val();
var currentPage = $('#currentPage').val();
var nextPage = CurrentPage + 1;
if (ifFound == 'false') {
//INCREMENT PAGE
$('#currentPage').attr('value', nextPage);
//GRAB DATA AGAIN
var websiteURL = $('#websiteURL').val();
var searchTerms = $('#searchTerm').val();
//INSERTS A + FOR EVERY SPACE
var searchTerms = searchTerms.replace(" ", "+");
//SEARCH GOOGLE
searchGoogle(searchTerms, nextPage);
}
});
}
Basically if it doesn't find the desired link on the first page of results, it should go on to the next page. I have a working fiddle for just the first page here: http://jsfiddle.net/p8DY3/1/
so how can I get searchGoogle() to run until it finds what it's looking for?
Maybe there's a better way of going about it that's in the Google API that I don't know about?
Sorry if my question is amateur, I've only begun to learn JavaScript on my own a month ago.
First you should make sure you are not hitting some sort of rate limit from Google. Look at your Network tab to see if the requests are coming back correctly.
First issue I see is bad math.
var currentPage = $('#currentPage').val(); is a string
Here var nextPage = CurrentPage + 1; you are treating it as a number.
You got to convert the string to a number before you add to it. You are doing string concatenation!.
var nextPage = parseInt(CurrentPage,10) + 1;
NITPICKS
Do not store things in inputs. Use variables, it will make things go so much faster. DOM lookup/manipulation is slow.
Why are you reinventing addClass and val()
$('.resultLink:last').attr('class', 'yourLink');
$('#currentPage').attr('value', nextPage);
should be
$('.resultLink:last').addClass('yourLink');
$('#currentPage').val(nextPage);
Why are you grabbing the search terms and website url again? You already have them passed into the function?
Finally your problem
searchGoogle(searchTerms, nextPage); <-- What are you missing here?
function searchGoogle(searchTerms, pageNumber, websiteURL) { <--what it is expecting
You are passing in 2 things when it wants 3.

Can someone explain why I get "Object Required" error

"I posted a similar question the other day and Thanks to #Alnitak for helping! However, I'm trying to enable/disable/enable 2 links (a href) between 2 given times and receive the "Object required" error. It's like the id's used lose focus. page_load function is called via onload. nStart & nExpired equal Start and End times and I'm using SetInterval instead of setTimeout (I modified Alnitak's code).
I wouldn't have a problem if these were buttons or if I could use PHP, but 'powers that be' would like it via hyperlink. Please tell me it's possible.. LOL
The error occurs the first line of the second IF condition i.e. making link visible.
var myInterval;
function page_load() {
myInterval = setInterval(function(){ShowLink()},60000);
}
function ShowLink() {
var now = new Date();
var clock = now.toTimeString();
var nStart = 1310;
var nExpired = 1312;
var MigTime = 60 * now.getHours() + now.getMinutes();
var disable = (day === 0 && (MigTime >= nStart && MigTime < nExpired));
if (disable == true) {
//hide links
document.getElementById("prdlnk").style.visibility = "hidden";
document.getElementById("viewlnk").style.visibility = "hidden";
document.getElementById("MigMsg").innerHTML= "Scheduled Migration in Progress. Please try later.";
}
if (MigTime > nExpired) {
//visible
document.getElementById("prdlnk").style.visibility = "visible";
document.getElementById("viewlnk").style.visibility = "visible";
document.getElementById("MigMsg").innerHTML= "";
// clearInterval(myInterval);
}
}
Thanks in advance,
Vernon
Could be a bad copy paste, but this line is missing a '
document.getElementById(prdlnk').style.visibility = "visible";
Should be
document.getElementById('prdlnk')...
Also, why are you mixing quotes and double quotes? Pick a style and stick with it.
var disable = (day === 0 && (MigTime >= nStart && MigTime < nExpired));
In this line of code, what is the intent behind day === 0? === is a test for object type and value, it's not an assignment operator. try day = 0

Categories