Looping JS Function Until if or else Condition is Met - javascript

I have created a function that when called with certain parameters will play out and then either tell the player they won or died based on the math. I have tested the function and it properly works, I set the parameters in the function calling so that the player would win and it displayed the win message alert. But if I increase the monster health to where the player will not win the first time the function is called, the function will not repeat itself as I tried to set up with the "else fight()" (as seen below).
My question is this, how to loop the function with the same parameters as before until the else or else if arguments are met?
Calling the function in HTML:
<a class="button" onclick="javascript:fight(8, 5, 1, 10, 3);">Test fight</a><br>
JS Function:
var playerHealth = 100;
function fight(monsterHealth, monsterDmg, monsterArmor, itemDmg, armorSts) {
newPlayerHealth = playerHealth - monsterDmg / armorSts + 2;
newMonsterHealth = monsterHealth - itemDmg / monsterArmor + 2;
if (playerHealth <= 0) {
alert('You died...');
}
else if (newMonsterHealth <= 0) {
alert('You won!');
}
else
{
var newPlayerHealth = playerHealth;
fight(newMonsterHealth, monsterDmg, monsterArmor, itemDmg, armorSts);
}
}
EDIT: Updated function and it says you win no matter how high the monster's damage or health is.

In your code playerHealth was intialize inside function which was resetting players health to 100 in recursive call.
newMonsterHealth was also not persisted considering recursive calls.
<html>
<head>
<script>
var playerHealth = 100;
function fight(monsterHealth, monsterDmg, monsterArmor, itemDmg, armorSts) {
playerHealth = playerHealth - Math.floor(monsterDmg / armorSts) ;
console.log("player helath"+playerHealth);
monsterHealth = monsterHealth - Math.floor(itemDmg / monsterArmor) ;
console.log("monster helath"+monsterHealth);
if (playerHealth <= 0) {
alert('You died...');
}
else if (monsterHealth <= 0) {
alert('You won!');
}
else fight(monsterHealth, monsterDmg, monsterArmor, itemDmg, armorSts);
}
</script>
</head>
<body>
<a class="button" onclick="javascript:fight(100, 40, 1, 10, 3);">Test fight</a><br>
</body>
</html>
P.S. I removed your +2 logic (I won't understood why you were adding)

You probably need for a recursive function that ends when certain condition will be met.
here is an example:
function recursive(target, i) {
target.innerHTML += '<br>' + i;
console.log('index is', i);
if(i > 100) {
return target.innerHTML += '<br>' + 'END';
}
return recursive(target, ++i);
}
document.addEventListener('DOMContentLoaded', function() {
recursive(document.getElementById('test'), 0)
});
<h1 id="test"></h1>

Related

JS: setInterval running as if there were multiple intervals set

So, I've been trying to figure out JS, and what better way to do so than to make a small project. It's a small trivia game, and it has a question timer I've made using setInterval. Unfortunately, after answering multiple questions, the interval's behaviour gets very weird - it runs the command twice every time. I guess it's my faulty implementation of buttonclicks?
By the way, if my code is awful I am sorry, I've been desperate to fix the issue and messed with it a lot.
function startGame(){
if (clicked === true){
return;
}
else{
$("#textPresented").html("Which anthem is this?");
$("#button").css("display", "none");
currentCountry = getRndInteger(0,8);
console.log(currentCountry);
var generatedURL = anthemnflags[currentCountry];
console.log(generatedURL);
audios.setAttribute("src", generatedURL);
audios.play();
$("#button").html("I know!");
$("#button").css("display", "block");
$("#button").click(function () {
continueManager();
});
y=10;
console.log("cleared y" + y);
x = setInterval(function(){
y = y - 1;
console.log("Counting down..." + y)
}, 1000);
console.log("INTERVAL SET");
}
}
Here is the console output:
cleared y10 flaggame.js:59:17
INTERVAL SET flaggame.js:64:17
AbortError: The fetching process for the media resource was aborted by the user agent at the user's request. flaggame.js:49
Counting down...9 flaggame.js:62:20 ---- THESE TWO ARE BEING PRINTED AT THE SAME TIME
Counting down...8 flaggame.js:62:20 ---- THESE TWO ARE BEING PRINTED AT THE SAME TIME
Counting down...7 flaggame.js:62:20
Counting down...6 flaggame.js:62:20
Counting down...5 flaggame.js:62:20
Counting down...4 flaggame.js:62:20
Counting down...3 flaggame.js:62:20
Counting down...2 flaggame.js:62:20
Counting down...1 flaggame.js:62:20
Counting down...0
THE REST OF MY CODE:
function middleGame(){
$("#button").css("display", "none");
var n = document.querySelectorAll(".flagc").length;
correctIMG = getRndInteger(0,n-1);
showFlags();
var taken = new Array();
for (var i = 0; i < n; ++i){
if (i === correctIMG){
images[i].attr("src", "res/" + flagsfiles[currentCountry]);
taken[currentCountry] = true;
}
else{
var randomFlag = getRndInteger(0, flagsfiles.length);
if (randomFlag !== currentCountry && taken[randomFlag] !== true){
images[i].attr("src", "res/" + flagsfiles[randomFlag]);
taken[randomFlag] = true;
}
}
}
$(".flagc").click(function(){
clickregister(this);
});
}
function continueManager(){
if (!clicked){
audios.pause()
clearInterval(x);
x = 0;
clicked = true;
middleGame();
return;
}
}
function clickregister(buttonClicked){
if ($(buttonClicked).attr("id") != correctIMG){
points = points - 1;
flagARR[$(buttonClicked).attr("id")].css("display", "none");
console.log("INCORRECT");
}
else{
if (y >= 0) {
var addedPoints = 1 + y;
points = points + addedPoints;
$("#points").html(points);
}
else{
points = points + 1;
}
hideFlags();
clicked = false;
startGame();
}
}
$(function(){
hideFlags();
$("#textPresented").html("When you're ready, click the button below!");
$("#button").html("I am ready!");
$("#button").click(function () {
if (!gameStarted){
gameStarted = true;
alert("STARTING GAME");
startGame();
}
});
});
Basically this is how it works:
When the "I am ready" button is clicked, startGame() is called. It plays a random tune and counts down, until the player hits the "I know" button. That button SHOULD stop the interval and start the middleGame() function, which shows 4 images, generates a random correct image and awaits input, checks if it's true, then launches startGame() again.
The first and second cycles are perfect - after the third one things get messy.
I also noticed that the "INCORRECT" log gets printed twice, why?
EDIT: here is the minimized code that has the same issue:
var x;
var gameStarted = false;
var y;
var clicked;
$(function(){
$("#button").click(function () {
if (!gameStarted){
gameStarted = true;
startGame();
}
});
});
function startGame(){
console.log("startgame()");
if (clicked === true){
return;
}
else{
console.log("!true");
$("#button").css("display", "block");
$("#button").click(function () {
continueManager();
});
y=10;
x = setInterval(function(){
y = y - 1;
console.log(y);
}, 1000);
}
}
function continueManager(){
if (!clicked){
clearInterval(x);
x = 0;
clicked = true;
middleGame();
return;
}
}
function middleGame(){
$("#button").css("display", "none");
var taken = new Array();
$(".flagc").click(function(){
clickregister(this);
});
}
function clickregister(buttonClicked){
console.log("clickgregister");
//Irrelevant code that checks the answers
clicked = false;
startGame();
}
EDIT2: It appears that my clickregister() function gets called twice, and that function then calls startGame() twice.
EDIT3: I have found the culprit! It's these lines of code:
$(".flagc").click(function(){
console.log("button" + $(this).attr("id") + "is clicked");
clickregister(this);
});
They get called twice, for the same button
I fixed it!
It turns out all I had to do was to add
$(".flagc").unbind('click');
Before the .click() function!
You need to clear the interval first then call it again. You can do that by creating a variable outside of the event listener scope and in the event listener check if the variable contains anything if yes then clear the interval of x. After clearing the interval you can reset it.
Something like this:
<button class="first" type="submit">Button</button>
const btn = document.querySelector('.first');
let x;
btn.addEventListener("click", () => {
x && clearInterval(x)
x = setInterval(() => console.log("yoo"), 500)
})
This is because if you don't clear the interval of x it will create a new one on every button press.

Wrote two JavaScript functions but one of them seems to be crashing my website

So I wrote a simple program that should change the background of a given website after 3 seconds.
Now this is my JavaScript code:
//this function changes the backgrounds after 3 seconds and increments n
function changebackgroundTimed(startvariable)
{
var n = startvariable;
var loop = 1;
while (loop == 1)
{
setTimeout(function(){changebackground(n)}, 3000)
n++;
if (n == 5)
{
n=1;
}
}
}
//this function changes the background depending on the given number
function changebackground(number)
{
if (number == 1)
{
$("body").css("background","no-repeat center/120%url('../images/1.jpg')");
}
else if (number == 2)
{
$("body").css("background","no-repeat center/120%url('../images/2.jpg')");
}
else if (number == 3)
{
$("body").css("background","no-repeat center/120%url('../images/3.jpg')");
}
else if (number == 4)
{
$("body").css("background","no-repeat center/120%url('../images/4.jpg')");
}
else {
$("body").css("background","no-repeat center/120%url('../images/1.jpg')");
}
}
in the html I just call it with: changebackgroundTimed(2);
Problem is: When I start the page it just loads for a long while and then eventually crashes while showing nothing. It has to do something with these two functions. Does anybody of you notices a mistake I may be missing?
Looks like you are not updating your "loop" variable, which is causing it go in an infinite loop.
Instead of using the while loop, use setInterval() method. That should do the work for you.
Keep the variable n outside the function, and refer it using outsiders this keyword.
function abc(){
var self = this;
self.n = 1;
setInterval(function () {
if(self.n ===1){
self.n = self.n + 1;
// your code
}else if(){
// and so on
}
changebackground(self.n);
},3000);
}
my 2 cents...
CSS:
:root {
--bg-images :"../images/1.jpg|../images/2.jpg|../images/3.jpg|../images/4.jpg|../images/5.jpg";
--img-bg :url(../images/1.jpg);
}
body {
background-image: var(--img-bg);
background-position: center/120%;
background-repeat: no-repeat;
}
Javascript:
const Root = document.documentElement
, gRoot = getComputedStyle(Root)
, imgList = gRoot.getPropertyValue('--bg-images').match(/"(.*?)"/)[1].split('|')
, NbImgs = imgList.length
, regExp = /\(([^)]+)\)/ // to get img url between parentheses
;
let currImg = imgList.findIndex(img=>img=== (regExp.exec(gRoot.getPropertyValue('--img-bg'))[1]))
;
setInterval(() => {
currImg = ++currImg % NbImgs;
Root.style.setProperty('--img-bg', `url(${imgList[currImg]})`)
}, 3000)
It appears that you never exit your while loop which makes the page crash as it runs forever. At some part in your code you have to change the value of the loop variable.
You are creating an infinite loop and never breaking out of it so it will run as fast as possible and lock up the UI.
Why not use setInterval like so:
const backgroundChanger = setInterval(changeBackground, 3000)
let background = 1
function changeBackground() {
if (background >= 5) background = 1
// set background however you like
document.body.style.background = 'url(../images/' + background++ + '.jpg) center/120% no-repeat'
}
setTimeout is a non-blocking call. This means that your code wont wait for 3000ms and keep on running in an infinte loop while calling changebackground(n);.
To read more about setTimeout go here setTimeout from MDN
Use the following code:
function changebackgroundTimed(startvariable)
{
var n = startvariable;
setInterval(() => {
changebackground(n);
n = (n % 5) + 1;
}, 3000);
}
//this function changes the background depending on the given number
function changebackground(number)
{
console.log(number)
}
changebackgroundTimed(2)
var i = 1;
setInterval(() => {
if(i > 5){
i = 0;
}else{
changeBackground(i)
}
i++;
}, 250);
function changeBackground(i){
switch(i){
case 1 :
$("body").css("color", "red")
break;
case 2 :
$("body").css("color", "blue")
break;
case 3 :
$("body").css("color", "green")
break;
case 4 :
$("body").css("color", "black")
break;
case 5 :
$("body").css("color", "orange")
break;
default:
$("body").css("color", "white")
}
}
A variation on a theme perhaps but without the hardcoded limitation of the original functions
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8' />
<title>Background image cycling</title>
<style>
body{
width:100%;
height:100vh;
padding:0;
margin:0;
}
</style>
<script>
/* configuration */
const PAUSE=3;
const SLIDES=['//www.stevensegallery.com/800/600','//www.placecage.com/800/600','//placebear.com/640/360','//picsum.photos/800/600'];
(function( delay ){
document.addEventListener('DOMContentLoaded',()=>{
var images=[/* default images */
'//placekitten.com/900/800',
'//placekitten.com/1000/800',
'//placekitten.com/1024/768',
'//placekitten.com/1200/800',
'//placekitten.com/1366/768'
];
if( arguments[1] )images=images.concat( arguments[1] )
var i=0;
(function(){
setTimeout( arguments.callee, 1000 * delay );
i++;
if( i > images.length - 1 )i=0;
document.body.style.background='center / contain no-repeat url('+images[ i ]+')';
})();
});
})( PAUSE, SLIDES );
</script>
</head>
<body>
<!-- content -->
</body>
</html>

"Uncaught TypeError:<function name> is not a function" after it got used once

I have a function, to make a div-Box "jump". The function works the first time, but after that I get the error "Uncaught TypeError: jump is not a function" after it gets used once. Can someone please explain why it doesn't work?
already = false;
function jump() {
if (already == false) { //So he can't jump 2 times in a row
try {
clearInterval(t); //<--this is my gravity function, where the div-Box falls down until it hits solid ground
} catch (err) {
console.log("not activated");
}
jump = setInterval("jump2();", 200);
already = true;
}
}
}
anz = 0;
function jump2() {
//Getting coordinates of div-Boy(they work)
var step = 10;
var bottom = getBottom("itBoy");
var right = getRight("itBoy");
var top = getTop("itBoy");
var left = getLeft("itBoy");
//lets see if he hits an object
if (anz <= 100) { //<-- anz = so he cant jump higher than 100 px
if (top - step >= 0) {
var a = hittest("itBoy", "up", 10); //if div wont hit a solid object --> return -1 | else return coordinates of bordes which collide (this function works too)
if (a == -1) {
anz += step;
document.getElementById("itBoy").style.top = (top -= step) + "px";
} else {
document.getElementById(itBoy).style.top = a + "px";
clearInterval(jump); // div stops moving upwards
t = setInterval("move('down');", 50); //gravity gets Activated again
}
}
} else {
clearInterval(jump);
t = setInterval("move('down');", 50);
}
}
It's because, you're overriding the jump:
function jump(){
// ...
jump = setInterval("jump2();",200);
// ^^ give it a different name
Also, a good approach to use function inside setInterval like:
setInterval(jump2, 200); // faster

My askQuestion() function in Javascript is always marking the answeras wrong

The link to my pen in CodePen - http://codepen.io/PartTimeCoder/pen/WwMxEX?editors=0010
The Javascript is here:
function randomNum(digits) {
return Math.floor(Math.pow(10, digits - 1) + Math.random() * 9 * Math.pow(10, digits - 1));
}
function askQuestion(digits) {
$(".result").html("");
var factor1 = randomNum(digits);
var factor2 = randomNum(digits);
var correctanswer = factor1 * factor2;
var answer = parseInt($(".answer").val(), 10);
console.log(correctanswer);
$(".question").html(factor1 + " × " + factor2);
var score = 0;
//Problem Starts Here
$(".check").click(function() {
if (correctanswer == answer) {
$(".result").html("Correct");
score += 1;
$(".score").html(score);
askQuestion(digits);
} else {
$(".result").html("Wrong");
score -= 1;
$(".score").html(score);
}
});
//Problem Ends Here
}
$(document).ready(function() {
$(".answer").hide();
$(".check").hide();
var digits = 0;
$(".digits-1").click(function() {
digits += 1;
});
$(".digits-2").click(function() {
digits += 2;
});
$(".digits-3").click(function() {
digits += 3;
});
$(".btn").click(function() {
$(".btn").hide();
$(".answer").show();
$(".check").show();
askQuestion(digits);
});
});
In between the comments is where I think the problem is. For example when it asks 4 x 9 and i input 36, it still marks it as wrong. I don't know why this is doing this. At first I though that the inputted information might still be a string so I used parseInt on it and it still didn't work. All help is appreciated. Thanks in advance!
The problem is that you're caching the initial value of the $('.answer') element in your answer variable. When you do $('.answer').val(), it saves what is there at that time, so if the user changes their answer afterwards, it won't be reflected in your variable. What you want to do is something like this:
// Rest of your code above
var answer = $(".answer");
console.log(correctanswer);
$(".question").html(factor1 + " × " + factor2);
var score = 0;
//Problem Starts Here
$(".check").click(function() {
// Don't check what is in the input until you're ready to use the value.
if (correctanswer == parseInt(answer.val(), 10)) {
$(".result").html("Correct");
score += 1;
$(".score").html(score);
askQuestion(digits);
} else {
$(".result").html("Wrong");
score -= 1;
$(".score").html(score);
}
});
The click handler has a reference to the old answer value. You must get the value from the input everytime. Something like this:
$(".check").click(function() {
var answer = parseInt($(".answer").val(), 10);
if (correctanswer == answer) {
$(".result").html("Correct");
score += 1;
$(".score").html(score);
askQuestion(digits);
} else {
$(".result").html("Wrong");
score -= 1;
$(".score").html(score);
}
});

How to execute something only once within if-loop?

EDIT (more specific):
I have the following situation. I am building an experimental synthesizer interface with JavaScript. I am using the howler.js plugin to handle audio.
The idea is that a sound is played if a certain condition is met. I have a number stored variable whose value changes via the heading of an iPhone compass.
I use compass.js. It has a variable called heading.
$(function(){
Compass.watch(function (heading) {
$('.degrees').text(Math.floor(heading) + '°');
$('#rotate').css('-webkit-transform', 'rotate(' + (-heading) + 'deg)');
if (heading >= 10 && heading <= 30) {
sound.play("myFirstSound");
}
else if (heading >= 31 && heading <= 60) {
sound.play("mySecondSound");
} else { etc etc...
});
});
Even if heading is constant the .play command is executed not once but over and over again causing the audio engine to run out of memory. How can I execute the commands within the if loops only once? I can not access the value of heading outside the compass function. I always get var heading does not exist error.
Take a Boolean variable like
var checkOnce= false;
if (myVariable >= 10 && myVariable <= 30) {
checkOnce=true;
sound.play("myFirstSound");
}
else if (myVariable >= 31 && myVariable <= 60) {
checkOnce=true;
sound.play("mySecondSound");
} else { etc etc...
In loop check the value of checkOnce. Loop will run till checkOnce=false
Create a boolean flag and play the sound only when false.
var isPlayed = false;
var oldValue = myVariable;
if (myVariable >= 10 && myVariable <= 30 && !isPlayed) {
sound.play("myFirstSound");
isPlayed = false;
}
Then initialize variable when needed... For what you say i guess you will need a variable to compare the values inside the loop and set isPlayed flag to false:
if (myVariable != oldValue)
isPlayed = false;
Just use a boolean variable.
var alreadyPlayed = false;
for(somecondition){
if(alreadyPlayed) continue;
sound.play("myFirstSound");
alreadyPlayed = true;
}
You get the idea.
The boolean value is a good idea. You could also make a use of the onend property of the sound object, so you will know when you can start playing the sound again. Like this:
var playing = false;
sound.onend = function () { playing = false; };
while (condition) {
if (!playing & ...)
...
...
}
Saw your edit to the question, add a delay to the next trigger, the compass might be firing too rapidly. Not familiar with compass.js but as I can see it, it fires per degree, and that is a lot... if you tilt the phone 180degrees thats 180 triggers.
Something like,
setTimeout(function() { your_func(); }, 50);
for a 50ms delay
You can also limit the triggers available from 360 to 36 or less by adding a counter of 10, 1 degree is for 1 count. So it triggers only after 10 degree movement.
$(function(){
var counter = 0;
Compass.watch(function (heading) {
if(counter <=10){
counter++;
}
else{
counter = 0;
$('.degrees').text(Math.floor(heading) + '°');
$('#rotate').css('-webkit-transform', 'rotate(' + (-heading) + 'deg)');
if (heading >= 10 && heading <= 30) {
sound.play("myFirstSound");
}
else if (heading >= 31 && heading <= 60) {
sound.play("mySecondSound");
} else { etc etc...
}
});
});
Add just a small layer of abstraction that will keep track of what's currently playing.
var nameOfSoundPlaying = null;
function play(soundName) {
if (nameOfSoundPlaying == soundName) {
return;
} else {
if (nameOfSoundPlaying != null) {
// stop currently playing sound (if still playing)
...
}
nameOfSoundPlaying = soundName;
sound.play(soundName);
}
}
Then it'll simplify your code :
$(function(){
Compass.watch(function (heading) {
$('.degrees').text(Math.floor(heading) + '°');
$('#rotate').css('-webkit-transform', 'rotate(' + (-heading) + 'deg)');
if (heading >= 10 && heading <= 30) {
play("myFirstSound");
}
else if (heading >= 31 && heading <= 60) {
play("mySecondSound");
} else { etc etc...
});
});

Categories

Resources