I'm learning JS by making a simple game Whack-a-mole. I have problem with randomPosition in this script:
//delete mole from every grid
function randomSquare() {
square.forEach((className) => {
className.classList.remove("mole");
})
let randomPosition = square[Math.floor((Math.random) * 9)];
randomPosition.classList.add("mole");
hitPosition = randomPosition.id;
}
square.forEach((id) => {
id.addEventListener("mouseup", () => {
if (id.id === hitPosition) {
result += 1;
score.textContent = result;
}
});
});
//move mole on the grid
function moveMole() {
let timerId = null;
timerId = setInterval(randomSquare, 1000);
}
moveMole();
function countDown() {
currentTime--;
timeLeft.textContent = currentTime;
if (currentTime === 0) {
clearInterval(timerId);
alert("Game over! Your final score is" + result);
}
}
let timerId = setInterval(countDown, 1000);
Error from console: TypeError: randomPosition is undefined app.js:15:3
Math.random function call is missing
let randomPosition = square[Math.floor((Math.random()) * 9)];
Related
I am new to programming and currently trying to build a timer. I am using JS when I encountered a problem on the very first line, it said that my function has already been declared when it hasn't. I read lots of other stack questions related to this error but none seems to have the same problem. Here is my code:
const timer = () => { // This is where the error occurs.
const song = document.querySelectorAll(".song");
const play = document.querySelector(".play");
const reset = document.querySelectorAll(".reset");
// Time display
const minuteDisplay = document.querySelectorAll(".minute");
const secondDisplay = document.querySelectorAll(".second");
//Duration
const formDuration = 20;
let duration = formDuration * 60;
let displayMinutes = ("0" + Math.floor(duration / 60)).slice(-2);
let displaySeconds = ("0" + Math.floor(duration % 60)).slice(-2);
for (const mdisplay in minuteDisplay) {
mdisplay.textContent = `${displayMinutes}`;
}
for (const sdisplay in secondDisplay) {
sdisplay.textContent = `${displaySeconds}`;
}
play.addEventListener("click", () => {
checkPlaying(song);
});
const checkPlaying = (song) => {
if (song.paused) {
song.play();
play.textContent = `Pause`;
play.classList.toggle("btn-active");
} else {
song.pause();
play.innerHTML = `Play`;
play.classList.remove("btn-active");
}
};
song.ontimeupdate = () => {
let currentTime = song.currentTime;
let elapsed = duration - currentTime;
let seconds = Math.floor(elapsed % 60);
let minutes = Math.floor(elapsed / 60);
// Edit time display
formatMinutes = ("0" + minutes).slice(-2);
formatSeconds = ("0" + seconds).slice(-2);
minuteDisplay.textContent = `${formatMinutes}`;
secondDisplay.textContent = `${formatSeconds}`;
reset.addEventListener("click", () => {
song.pause();
song.currentTime = 0;
play.innerHTML = `Play`;
play.classList.remove("btn-active");
if (reset.classList.contains("btn-active")) return;
reset.classList.add("btn-active");
// remove class after 2 seconds
setTimeout(() => {
reset.classList.remove("btn-active");
}, 150);
});
if (currentTime >= duration) {
song.pause();
song.currentTime = 0;
play.innerHTML = "Play";
play.classList.remove("btn-active");
}
};
};
timer();
I am confused as how to solve this error, so any feedback will be greatly appreciated. Thankyou!
Hi I checked your when you are using querySeelctorAll it returns array so before adding addEventListener you have to do for loop like this
let's do it for reset button:
const reset = document.querySelectorAll(".reset");
and now first for loop
reset.forEach(resetButton => {
resetButton.addEventListener("click", () => {
song.pause();
song.currentTime = 0;
play.innerHTML = `Play`;
play.classList.remove("btn-active");
if (reset.classList.contains("btn-active")) return;
reset.classList.add("btn-active");
// remove class after 2 seconds
setTimeout(() => {
reset.classList.remove("btn-active");
}, 150);
});
})
Or you can use loop like this:
for(const resetButton of reset) {
resetButton.addEventListener("click", () => {
song.pause();
song.currentTime = 0;
play.innerHTML = `Play`;
play.classList.remove("btn-active");
if (reset.classList.contains("btn-active")) return;
reset.classList.add("btn-active");
// remove class after 2 seconds
setTimeout(() => {
reset.classList.remove("btn-active");
}, 150);
});
}
Hope this would help.
And for debugging why "Uncaught SyntaxError: Identifier has already been declared error occurs" happens I need to have your HTML part too.
let me know when you updated the code
I am working on a whac a mole game where the background image should flash when the square is hit. For example an image that says "hit".
The square has been targeted correctly on function showImage(), tested with a console.log, and called in a forEach loop. I don't know the next step. I know I need to grab css class with background image of square and add an image. Maybe a set timer is involved. I have tried this and cannot get it working. See codepen
const squares = document.querySelectorAll('.square')
const mole = document.querySelector('.mole')
const timeLeft = document.querySelector('#time-left')
const score = document.querySelector('#score')
let result = 0
let hitPosition
let currentTime = 60
let timerId = null
function showImage() {
if ((document.onclick = squares)) {
squares.style.backgroundColor = 'green';
//console.log('it is working');
} else {
alert('it is not working');
}
}
function randomSquare() {
squares.forEach(square => {
square.classList.remove('mole')
})
let randomSquare = squares[Math.floor(Math.random() * 9)]
randomSquare.classList.add('mole')
hitPosition = randomSquare.id
}
squares.forEach(square => {
square.addEventListener('mousedown', () => {
if (square.id == hitPosition) {
result++
score.textContent = result
hitPosition = null
showImage();
}
})
})
function moveMole() {
timerId = setInterval(randomSquare, 500)
}
moveMole()
function countDown() {
currentTime--
timeLeft.textContent = currentTime
if (currentTime == 0) {
clearInterval(countDownTimerId)
clearInterval(timerId)
alert('GAME OVER! Your final score is ' + result)
}
}
let countDownTimerId = setInterval(countDown, 1000)
Sounds like this could handled by a class that displays an image in the background.
.bg-img {
background-image: url('');
}
And then in the function showImage() you set the class name bg-img on the element:
function showImage() {
squares.classList.add('bg-img');
setTimeout(function(){
squares.classList.remove('bg-img');
}, 1000);
}
And remove the class name again 1000 ms after.
I need to exit from a running interval if the conditions are correct:
var refreshId = setInterval(function() {
var properID = CheckReload();
if (properID > 0) {
<--- exit from the loop--->
}
}, 10000);
Use clearInterval:
var refreshId = setInterval(function() {
var properID = CheckReload();
if (properID > 0) {
clearInterval(refreshId);
}
}, 10000);
Pass the value of setInterval to clearInterval.
const interval = setInterval(() => {
clearInterval(interval);
}, 1000)
Demo
The timer is decremented every second, until reaching 0.
let secondsRemaining = 10
const interval = setInterval(() => {
// just for presentation
document.querySelector('p').innerHTML = secondsRemaining
// time is up
if (secondsRemaining === 0) {
clearInterval(interval);
}
secondsRemaining--;
}, 1000);
<p></p>
Updated for ES6
You can scope the variable to avoid polluting the namespace:
const CheckReload = (() => {
let counter = - 5;
return () => {
counter++;
return counter;
};
})();
{
const refreshId = setInterval(
() => {
const properID = CheckReload();
console.log(properID);
if (properID > 0) {
clearInterval(refreshId);
}
},
100
);
}
I am working on knockout js.
In that i have a recursive function which executes a function every minute. for that am using a timer every 60 sec it will execute also same will be reflecting in the UI also.
In my case, if i try to assign or initialize a timer value(observable) which is inside a loop, it doesn't reflecting instead of reflecting it is added to the pipeline and that much time loop is running simultaneously.
In that case i want to kill the loop and again want to restart every time i am changing the timer value.
timerInSec=60;
var loop = function () {
if (this.timer() < 1) {
myFunction()
this.timer(this.timerInSec - 1);
setTimeout(loop, 1000);
} else {
this.timer(this.timer() - 1);
setTimeout(loop, 1000);
}
};
loop();
Here is my solution. Please check.
timerInSec = 60;
const Loop = (function () {
let timer = 0;
let timerId = -1;
const myFunction = function () {
console.log('finished');
}
const fnLog = function (tm) {
console.log('current time = ', tm);
}
const fnProc = function () {
timerId = setTimeout(myFunction, 1000 * timer);
}
return {
start: function (tm = 60) {
this.stop();
timer = tm;
fnProc();
},
stop: function () {
if (timerId !== -1) {
clearTimeout(timerId);
timerId = -1;
}
}
}
})();
Loop.start(timerInSec);
setTimeout(() => {
Loop.start(timerInSec);
}, 500);
window.setInterval(function(){
//do stuff
}, milisec);
Is there a way to stop this interval at will, and to resume it from where it lasted? Say, code runs every 5 sec. I stop it in the middle of the 2nd second, when resumed, I want it to run the remaining 3 seconds and continue to run afterwards every 5 sec. again.
Try this:
1- when you want to pause the timer, calculate the remaining milliseconds and store it somewhere then call clearInterval.
2- When you want to resume the timer, just make a call to setTimeout passing the remaining time stored in the previous step as the argument.
3- And in setTimeout's callback you should call setInterval again.
UPDATE: This is what you want, a changed version of javascript: pause setTimeout(); thanks to #Felix Kling
function IntervalTimer(callback, interval) {
var timerId, startTime, remaining = 0;
var state = 0; // 0 = idle, 1 = running, 2 = paused, 3= resumed
this.pause = function () {
if (state != 1) return;
remaining = interval - (new Date() - startTime);
window.clearInterval(timerId);
state = 2;
};
this.resume = function () {
if (state != 2) return;
state = 3;
window.setTimeout(this.timeoutCallback, remaining);
};
this.timeoutCallback = function () {
if (state != 3) return;
callback();
startTime = new Date();
timerId = window.setInterval(callback, interval);
state = 1;
};
startTime = new Date();
timerId = window.setInterval(callback, interval);
state = 1;
}
Usage:
var timer = new IntervalTimer(function () {
alert("Done!");
}, 5000);
window.setTimeout(function () {
timer.pause();
window.setTimeout(function () {
timer.resume();
}, 5000);
}, 2000);
To piggyback off Alireza's answer, here's an ES6 class that does the same thing with a bit more functionality, and doesn't start right away. You can set a maximum number of times the timer will fire off before automatically stopping, and pause and resume any number of times before the next time it's set to fire off.
export default class IntervalTimer{
constructor(name, callback, interval, maxFires = null){
this.remaining = 0;
this.state = 0; // 0 = idle, 1 = running, 2 = paused, 3= resumed
this.name = name;
this.interval = interval; //in ms
this.callback = callback;
this.maxFires = maxFires;
this.pausedTime = 0; //how long we've been paused for
this.fires = 0;
}
proxyCallback(){
if(this.maxFires != null && this.fires >= this.maxFires){
this.stop();
return;
}
this.lastTimeFired = new Date();
this.fires++;
this.callback();
}
start(){
this.log.info('Starting Timer ' + this.name);
this.timerId = setInterval(() => this.proxyCallback(), this.interval);
this.lastTimeFired = new Date();
this.state = 1;
this.fires = 0;
}
pause(){
if (this.state != 1 && this.state != 3) return;
this.log.info('Pausing Timer ' + this.name);
this.remaining = this.interval - (new Date() - this.lastTimeFired) + this.pausedTime;
this.lastPauseTime = new Date();
clearInterval(this.timerId);
clearTimeout(this.resumeId);
this.state = 2;
}
resume(){
if (this.state != 2) return;
this.pausedTime += new Date() - this.lastPauseTime;
this.log.info(`Resuming Timer ${this.name} with ${this.remaining} remaining`);
this.state = 3;
this.resumeId = setTimeout(() => this.timeoutCallback(), this.remaining);
}
timeoutCallback(){
if (this.state != 3) return;
this.pausedTime = 0;
this.proxyCallback();
this.start();
}
stop(){
if(this.state === 0) return;
this.log.info('Stopping Timer %s. Fired %s/%s times', this.name, this.fires, this.maxFires);
clearInterval(this.timerId);
clearTimeout(this.resumeId);
this.state = 0;
}
//set a new interval to use on the next interval loop
setInterval(newInterval){
this.log.info('Changing interval from %s to %s for %s', this.interval, newInterval, this.name);
//if we're running do a little switch-er-oo
if(this.state == 1){
this.pause();
this.interval = newInterval;
this.resume();
}
//if we're already stopped, idle, or paused just switch it
else{
this.interval = newInterval;
}
}
setMaxFires(newMax){
if(newMax != null && this.fires >= newMax){
this.stop();
}
this.maxFires = newMax;
}
}
You should only need setTimeout with a go and stop - http://jsfiddle.net/devitate/QjdUR/1/
var cnt = 0;
var fivecnt = 0;
var go = false;
function timer() {
if(!go)
return;
cnt++;
if(cnt >= 5){
cnt=0;
everyFive();
}
jQuery("#counter").text(cnt);
setTimeout(timer, 1000);
}
function everyFive(){
fivecnt++;
jQuery("#fiver").text(fivecnt);
}
function stopTimer(){
go = false;
}
function startTimer(){
go = true;
timer();
}
let time = document.getElementById("time");
let stopButton = document.getElementById("stop");
let playButton = document.getElementById("play");
let timeCount = 0,
currentTimeout;
function play_pause() {
let status = playButton.innerHTML;
if (status == "pause") {
playButton.innerHTML = "Resume";
clearInterval(currentTimeout);
return;
}
playButton.innerHTML = "pause";
stopButton.hidden = false;
clearInterval(currentTimeout);
currentTimeout = setInterval(() => {
timeCount++;
const min = String(Math.trunc(timeCount / 60)).padStart(2, 0);
const sec = String(Math.trunc(timeCount % 60)).padStart(2, 0);
time.innerHTML = `${min} : ${sec}`;
}, 1000);
}
function reset() {
stopButton.hidden = true;
playButton.innerHTML = "play";
clearInterval(currentTimeout);
timeCount = 0;
time.innerHTML = `00 : 00`;
}
<div>
<h1 id="time">00 : 00</h1>
<br />
<div>
<button onclick="play_pause()" id="play">play</button>
<button onclick="reset()" id="stop" hidden>Reset</button>
</div>
</div>