I am writing a simple JavaScript game in which the user clicks on a div that plays sounds and if they guess the animal sound correct, a counter keeps track of their score and increments.
It works, but if the same sound is played again and the user guesses it increments the counter with two not one and sometimes even by three and I can't figure out why and how to fix it, here is my HTML:
<div id="counter">Score:<span id="counter-score"> 0</span> </div>
and here is the JavaScript code:
var sounds = [
{
animalType: 'horse',
sound: new Audio('../sounds/Horse-neigh.mp3')
},
{
animalType: 'bear',
sound: new Audio('../sounds/grizzlybear.mp3')
},
{
animalType: 'goat',
sound: new Audio('../sounds/Goat-noise.mp3'),
}
];
var player = document.getElementById('player');
var enteredWord = document.getElementById('entered-word');
var counter = document.getElementById('counter-score');
startGame();
function startGame() {
player.addEventListener('click', function() {
var sound = sounds[Math.floor(Math.random() * sounds.length)];
var currentSound = sound.animalType;
sound['sound'].play();
enteredWord.addEventListener('keydown', function() {
if (event.key === 'Enter') {
if (enteredWord.value === currentSound) {
counter.textContent++;
}
} else {
}
});
});
}
Why does it happen like that?
I tried using the += operator but it gives the same result.
As #Ibu said in comments, each time the click event occur, a new event listener is added to keydown event.
You should extract the enteredWord.addEventListener part outside of player.addEventListener callback, like so:
function startGame() {
var currentSound;
player.addEventListener('click', function() {
var sound = sounds[Math.floor(Math.random()*sounds.length)];
currentSound = sound.animalType;
sound['sound'].play();
})
enteredWord.addEventListener('keydown', function() {
if(event.key === 'Enter') {
if(enteredWord.value === currentSound) {
counter.textContent ++;
}
}
})
}
Hi can you replace your code with this one, I think the key-down event is executing twice so you are getting this issue.
enteredWord.addEventListener('keydown', function(e) {
if( (e.keyCode ==13) && (enteredWord.value === currentSound)){
e.stopImmediatePropagation();
counter.textContent ++;
}
})
Related
I was able to get the space bar to activate the simple button I made, but I am having problems with having it be looped with setInterval(). Is it because of the eventFire mechanism I utilized? Any help or constructive criticism is welcomed as I am trying to learn. Thanks for your time.
Edit: I was able to find a solution as I apparently was using the setInterval function incorrectly. However I still have an issue with stopping the setInterval loop with clearInterval(timer) E hotkey. Here is the updated code.
"use strict";
// used for Tracking Mouse Position which I will implement later
let mousex;
let mousey;
// Simple Button that logs the amount of times pressed and triggers an animation
function button() {
const button = document.querySelector(".button");
function buttonClassRemove() {
button.classList.remove("active");
}
function delay(time, inputFunction) {
let milliseconds = time;
setTimeout(function () {
inputFunction();
}, milliseconds);
}
let i = 0;
button.addEventListener("click", function () {
i = i + 1;
console.log(`Button pressed ${i} times`);
button.classList.add("active");
delay(100, buttonClassRemove);
});
}
// Simulates event
function eventFire(el, etype) {
if (el.fireEvent) {
el.fireEvent("on" + etype);
} else {
var evObj = document.createEvent("Events");
evObj.initEvent(etype, true, false);
el.dispatchEvent(evObj);
}
}
function autoClicker() {
document.addEventListener("mousemove", () => {
// Tracking Mouse Position
mousex = event.clientX; // Gets Mouse X
mousey = event.clientY; // Gets Mouse Y
// console.log([mousex, mousey]); // Prints data
});
document.body.onkeydown = function (e) {
// Simulates click mouse event
// and then loop that functionality with the setInterval function
let timer = setInterval(function () {
eventFire(document.getElementById("button1"), "click");
}, 100);
if (e.keyCode == 32) {
timer;
console.log("Space pressed");
} else if (e.keyCode == 69) {
// Cancels autoclicker setInterval function
clearInterval(timer);
console.log("E pressed");
}
};
}
autoClicker();
button();
Your issue is that timer is a local variable. You should define it with var at the start of your program alongside mousex and mousey. As it currently stands, every time you run onkeydown it creates a new instance of the interval, checks whether to cancel it, and then throws away the reference. If you make it a global variable, then you can keep the reference so that you can cancel it at any time.
With this, you should also consider when you are running the interval. If you keep it as-is, your old interval will be overwritten with the new one before you can check to cancel. What you should do instead is something like this:
var timer = null;
document.body.onkeydown = function(e) {
if (/* start timer */ && timer == null) {
timer = setInterval(/* ... */);
} else if (/* end timer */) {
clearInterval(timer);
timer = null;
}
}
I'm trying the make a chrome extension in javascript. So far, my popup.js looks like this:
let bg;
let clock;
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('button1').addEventListener('click', butClicked);
bg = chrome.extension.getBackgroundPage();
//clock = document.getElementById("label1");
});
let timeStamp;
let isClockRunning = false;
function butClicked() {
let test = bg.getURL();
document.getElementById('test').innerHTML = test;
timeStamp = new Date();
isClockRunning = !isClockRunning;
runCheckTimer();
}
function runCheckTimer() {
var handle;
if(isClockRunning == true) {
handle = setInterval(updateClock, 1000);
}
else if(isClockRunning == false) {
clearInterval(handle);
handle = 0;
}
}
function updateClock() {
let seconds = bg.returnTimeSince(timeStamp);
document.getElementById("label1").innerHTML = "Seconds: " + seconds;
}
The program works just fine when I click the button once; it starts the timer. But when I click the button the second time, timeStamp gets set to 0, but the updateClock keeps running at the same interval; the interval doesn't get cleared even though I'm toggling the isClockRunning boolean. It's almost as if javascript is forgetting to run the else if part in runCheckTimer(). How can I fix this?
EDIT: On a sidenote, am I doing the timer thing the right way? Or is there a better way to do it? I basically want a timer to keep ticking every second since you've pressed the button, and then when you click it again it'll stop and reset to 0.
You have scoped handle to runCheckTimer. When runCheckTimer starts, it will create a new handle every time.
Move handle outside of the function.
var handle;
function runCheckTimer() {
if(isClockRunning == true) {
handle = setInterval(updateClock, 1000);
}
else if(isClockRunning == false) {
clearInterval(handle);
handle = 0;
}
}
In my application, I am creating 10 audio object and I store them in a browser variable. Based on the scenario, I will pick one of them and will assign it to one global variable. Then I play that sound. After some time I will stop and clear that audio from the global variable.
I am verifying the readystate whenever I play the sound. I capture Play & Pause events.
The problem is that sometimes the sound is not audible but play & pause events are still fired. I am not able to reproduce it all the times (it happens randomly).
Please let me know if anyone faced this kind of issue.
function Sound(){}
try
{
if(new Audio().canPlayType("audio/wav") != "" && new Audio().canPlayType("audio/wav") != "no" ){
Sound.extn="wav";//No I18N
}
else{
Sound.extn="mp3";//No I18N
}
}
catch (e) {
}
Sound.init=function(soundsobj)
{
for (var tunename in soundsobj)
{
var audioobj = new Audio(soundsobj[tunename]);
audioobj.setAttribute("autobuffer", "true");
audioobj.addEventListener('loadeddata', function() {
Sound.cache[tunename]=this;
});
audioobj.addEventListener('loadedmetadata', function() {
this.currentTime = 0;
});
Sound._SOUND_PLAYER.addEventListener( "play", function(){console.log(this.readyState);}, false);
Sound._SOUND_PLAYER.addEventListener( "pause", function(){console.log(this.readyState);}, false);
}
}
Sound.playTune=function(tunename,duration)
{
if(Sound.cache[tunename])
{
var cachedsound = Sound.cache[tunename];
if(cachedsound.readyState == 4)
{
cachedsound.currentTime = 0;
Sound._SOUND_PLAYER = cachedsound;
Sound.play(duration);
}
else
{
delete Sound.cache[tunename];
var audioobj = new Audio(soundsobj[tunename]);
audioobj.setAttribute("autobuffer", "true");
audioobj.addEventListener('loadeddata', function() {
Sound.cache[tunename]=this;
Sound._SOUND_PLAYER = this;
Sound.play(duration);
});
}
}
}
Sound.play = function(duration)
{
Sound._SOUND_PLAYER.loop=true;
Sound._SOUND_PLAYER.play();
clearTimeout(Sound.cleartimer);
Sound.cleartimer = setTimeout(function(){Sound.stop()},duration*1000);
}
Sound.stop=function()
{
try
{
if(Sound._SOUND_PLAYER)
{
Sound._SOUND_PLAYER.pause();
}
Sound._SOUND_PLAYER = undefined;
}
catch(e){}
}
In my game I have a startGame() function which initializes all the key functions that start the game. At the beginning, you press the start button to take you to the game. Once the game is complete the restart button appears. When this is clicked it takes you back to the start screen, where the start button is.
Ideally I would like at this point to be able to click the start button for the second time, and a new game appear. The problem is that it brings the old game up. I have tried to use .empty, .queue and .dequeue and reset, but nothing seems to work.
How can I restart all the functions when the restart-button is clicked?
$(document).ready(function () {
successSound = $("#successSound")[0];
failSound = $("#failSound")[0];
moveSound = $("#moveSound")[0];
hitSound = $("#hitSound")[0];
missSound = $("#missSound")[0];
hintSound = $("#hintSound")[0];
hintPic = $("#hintPic")[0];
hintPicTitle = $("#hintPicTitle")[0];
bgMusic = $('#audio-bg')[0];
newGame();
//Click event to start the game
$(".start-btn-wrapper").click(function () {
startplay();
});
//Click event to restart the game
$(".restart-btn").click(function () {
restartplay();
});
Fiddle with script in: http://jsfiddle.net/rVaFs/
It will be much easier if you stop using globals: everything not prefixed with var (including functions).
If your startplay depends on initial DOM state, and it’s too difficult (or just takes too much time) to rewrite the code, you can just make a copy of that part of DOM before starting game and delete it on finish.
You could use the document.ready callback to reset everything back to it's original state, by naming the callback function:
$(document).ready(function reset()
{
//your code here
//note, you must ensure event handlers are unbound:
$('#reset').unbind('click').bind('click',reset);//<-- call main callback
});
Another thing you have to keep in mind is that you're creating a lot of implied globals, which could cause problems if you were to use the ready callback. To address this, do change these lines: successSound = $("#successSound")[0]; to var successSound = $("#successSound")[0];.
I created a function called resetGame() and cleared the DOM:
function resetGame() {
$(document).ready();
$('.table-container').empty();
$('.reveal-wrapper').empty();
$('.helper').removeClass('inactive');
$('.tiles-wrapper').removeClass('active');
$('.hint-container').removeClass('active');
$('td').addClass('highlight-problem');
$('.game').removeClass("active").removeClass('game-over').addClass('standby').addClass('transition');
$('.score').html("");
$(".next-question").removeClass('move-down');
$('.reveal-wrapper').removeClass('image' + randomNumber);
$(bgMusic).unbind();
score.right = 0;
score.wrong = 0;
}
function newGame() {
randomWord = [];
listOfWords = [];
attemptNumber = [];
completionNumber = [];
populationNumber = [];
gridSize = [];
createGrid();
backGroundImage();
dragEvent();
nextQuestion();
closeMessage();
replaySound();
$('.score').html("0/" + completionNumber);
$('.game').removeClass("standby").addClass('active').addClass('transition');
$(bgMusic).on('timeupdate', function () {
var vol = 1,
interval = 250;
if (bgMusic.volume == 1) {
var intervalID = setInterval(function () {
if (vol > 0) {
vol -= 0.05;
bgMusic.volume = vol.toFixed(2);
} else {
clearInterval(intervalID);
}
}, interval);
}
});
}
$(document).ready(function () {
successSound = $("#successSound")[0];
failSound = $("#failSound")[0];
moveSound = $("#moveSound")[0];
hitSound = $("#hitSound")[0];
missSound = $("#missSound")[0];
hintSound = $("#hintSound")[0];
hintPic = $("#hintPic")[0];
hintPicTitle = $("#hintPicTitle")[0];
bgMusic = $('#audio-bg')[0];
backGroundSound();
playBackGroundSound();
keyPress();
$(".start-btn-wrapper").click(function () {
newGame();
});
$(".restart-btn").click(function () {
resetGame();
});
});
I then called it in the restart-btn click event.
I am revisiting this code I made a year ago with the help of another person. Unfortunately I don't have contact with them anymore to get more help. Basically It dynamically adds classs to the tb and b nodes of a document coming from namesToChange. Now what I am trying to do is append some text to the div with class dtxt node but still use this code below. I am using the code $('td.pn_adm_jeff').children('div.dtxt').append('zzz'); and it works but it constantly appends more than once as seen in the photo below. How do I go about making it add once and stop?
Photo
http://img6.imageshack.us/img6/5392/7c23ddb145954aefadb1b9f.png
Code
function customizefields(a) {
$('td b').each(function () {
name = $(this).text();
if (name.indexOf(" ") != -1) {
name = name.substring(0, name.indexOf(" "))
}
if (a[name]) {
this.className = a[name].class;
this.parentNode.className = a[name].img
}
})
$('td.pn_adm_jeff').children('div.dtxt').append('zzz');
}
var namesToChange = {
'Jeff' :{'class':'pn_adm','img':'pn_adm_jeff'}
};
setInterval(function () {
customizefields(namesToChange)
}, 1000);
Update
var needsUpdate = true;
function customizefields(a) {
$('td b').each(function () {
name = $(this).text();
if (name.indexOf(" ") != -1) {
name = name.substring(0, name.indexOf(" "));
}
if (a[name]) {
this.className = a[name].class;
this.parentNode.className = a[name].img;
}
});
if (needsUpdate) {
$('td.pn_adm_jeff').children('div.dtxt').append('testing');
needsUpdate = false;
}
}
var namesToChange = {
'jeff' :{'class':'pn_adm','img':'pn_adm_jeff'};
};
setTimeout(function () {
customizefields(namesToChange);
}, 1000);
use setTimeout rather than setInterval (interval is for repeating a timer task, timeout is a single timer task)
To prevent a certain task from occuring more than once in a repeated task, there is a simple fix.
// global variable
var needsUpdate = true;
// now in the timer task
if (needsUpdate) {
$('td.pn_adm_jeff').children('div.dtxt').append('zzz');
needsUpdate = false;
}
Does that work for you?
Define a global variable to hold the input flag
var appended = false;
function appendthestring() {
if(!appended) $('td.pn_adm_jeff').children('div.dtxt').append('zzz');
appended = true;
}