How to create a Scramble function in Javascript - 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.

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

How to write a script, which when ran increments the number by 1?

I am looking to write a script in windows which when the user clicks on it increments the count by 1.
The script has a variable integer 000000
Another variable string number
When the user clicks on the script from their desktop it increments the integer to 000001 and appendes in front of number so it becomes number000001 and when user next clicks it increases to number000002 and so on.
I am sure it's a simple script but I am not sure where to begin or which language to use, it'll be great if someone can help me out
I think it would be something along the lines of, but not really sure what I am doing, how to save the increment from last run, how to run trigger the script when the icon is clicked from desktop.
Integer = 000000
String = "Number"
Integer++
IntegerString = Number+Integer
Thanks.
did you need this 00000 before 1,2,3 and etc?
try this one:
let i = 0;
const s = 'Number';
const intString = s + format(i++);
and formatter for number:
function format(num) {
let strNum = num.toString();
const numSize = strNum.length;
for(i = numSize; i < 6; i++) {
strNum = 0 + strNum;
}
}
Take a look at this probably solves your problem
let dv = "000000";
document.querySelector('button').addEventListener('click', () => {
let increment = ++dv;
increment = ("000000" + increment).slice(-6);
document.querySelector('span').innerText = increment
})
<button>increment</button>
<h1>number<span>000000</span></h1>

How to choose and work with a drop down list in js

I got this problem. I created a drop down list for choosing the algorithm to work with. It works with the first option but not all of them. Could you please help me?
Thanks in advance
var form1 = document.getElementById('form1');
var form2 = document.getElementById('form2');
var form3 = document.getElementById('form3');
var formArray = [];
formArray.push(form1.innerHTML);
formArray.push(form2.innerHTML);
formArray.push(form3.innerHTML);
//select drop down list//
function changeToCal() {
dropDownList.selectedIndex--;
document.getElementById('form').innerHTML = formArray[dropDownList.selectedIndex];
}
//Calculate //
document.getElementById('form').addEventListener("submit",
function(event) {
var fieldy = document.getElementById('fieldy');
var fieldx = document.getElementById('fieldx');
var resultField = document.getElementById('resultField');
var x = parseFloat(fieldx.value);
var y = parseFloat(fieldy.value);
if(!fieldy.value || !fieldx.value) {
alert("Please enter numbers in the fields!");
} else if (dropDownList.selectedIndex = 1) {
var result = (y / 100) * x;
resultField.innerText = "Answer: " + result + "."
event.preventDefault();
} else if (dropDownList.selectedIndex = 2) {
var result = (100 / y) * x;
resultField.innerText = "Answer: " + result + "."
event.preventDefault();
} else if (dropDownList.selectedIndex = 3) {
var result = (y / x) * 100;
resultField.innerText = "Answer: " + result + " %."
event.preventDefault();
} else {
resultField.innerText = "Error"
event.preventDefault();
}
}
);
https://codepen.io/anon/pen/VMZNwQ
This line:
} else if (dropDownList.selectedIndex = 1) {
needs to use a comparison equals operator rather than an assignment equals operator:
} else if (dropDownList.selectedIndex === 1) {
The other if/else clauses are similarly incorrect.
I highly recommend using a decent IDE, it would highlight potential mistakes like this for you.
You will also need to change this:
dropDownList.selectedIndex--;
document.getElementById('form').innerHTML = formArray[dropDownList.selectedIndex];
to this:
document.getElementById('form').innerHTML = formArray[dropDownList.selectedIndex - 1];
The selectedIndex is live, if you change it using -- that will cause the selected value to be updated.
The way the result is output assumes there is an <h3> with the id resultField but only one of your forms has that id set.
Other miscellaneous suggestions include...
The id attributes need to be unique throughout the document. You currently have 3 hidden forms and you copy around the HTML, leading to 4 elements with each id (resultField, fieldx, fieldy). Whether document.getElementById grabs the right one is down to luck.
Rather than copying around the innerHTML of those forms you'd be better off simply showing and hiding the existing forms using CSS. Alternatively you could use just 1 form and update the relevant text to match the current algorithm.
Listening for the submit event of the form seems odd. Why not use a regular button and listen for the click event?
If you do decide to keep the 3 forms I would suggest registering separate button handlers for each one. The fact that so much of your code is inside a big if/else is a sign that you actually need multiple functions rather than a single function that has to figure out which mode it is in. The code they share could be factored out if appropriate.

How to retrigger a function with timeOuts via key input

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?

In Qualtrics surveys, how can you use JavaScript to dynamically set the range of a slider?

I was making a survey in Qualtrics, and needed to have my items show different values of the slider depending on a variable, in my case, the value from a loop and merge. That didn't seem like a thing that you could do with piped text, so I had to figure out how to do it in Javascript.
I'm just posting this as an opportunity to provide the answer I found on my own. As usual with Qualtrics, your mileage may vary, and this may need to be modified for your specific situation. In particular, the question IDs and postTags change depending on whether it is in a loop/merge, and perhaps on other factors.
Put the following code into the javascript section of the question:
// Set the slider range
// First define the function to do it
setSliderRange = function (theQuestionInfo, maxValue) {
var postTag = theQuestionInfo.postTag
var QID=theQuestionInfo.QuestionID
// QID should be like "QID421"
// but postTag might be something like "5_QID421" sometimes
// or, it might not exist, so play around a bit.
var sliderName='CS_' + postTag
window[sliderName].maxValue=maxValue
// now do the ticks. first get the number of ticks by counting the table that contains them
var numTicks = document.getElementsByClassName('LabelDescriptionsContainer')[0].colSpan
// do the ticks one at a time
for (var i=1; i<=numTicks; i++) {
var tickHeader='header~' + QID + '~G' + i
// the first item of the table contains the minimum value, and also the first tick.
// so we do some tricks to separate them out in that case.
var tickSpanArray = $(tickHeader).down("span.TickContainer").children
var tickSpanArrayLength=tickSpanArray.length
var lastTickIndex=tickSpanArrayLength - 1
var currentTickValue = tickSpanArray[lastTickIndex].innerHTML
currentTickValue=currentTickValue.replace(/^\s+|\s+$/g,'')
console.log('Tick value ' + i + ' is ' + currentTickValue)
// now get the new value for the tick
console.log('maxValue: ' + maxValue + ' numTicks: ' + numTicks + ' i: ' + i)
var newTickValue = maxValue * i / numTicks //the total number of ticks
tickSpanArray[lastTickIndex].innerHTML=newTickValue.toString()
console.log('Changed tick value to ' + newTickValue)
}
}
var currentQuestionInfo = this.getQuestionInfo()
var currentQuestionID = currentQuestionInfo.QuestionID
// Now call the function
setSliderRange(currentQuestionInfo, theMaxValueYouWant)
If you find my answers helpful, help raise my reputation enough to add "qualtrics" as a valid tag!! Or, if someone else with reputation over 1500 is willing to do it that would be very helpful!

Categories